Flutter 中的Provider

Provider 本质不是“状态管理框架魔法”,而是对 Flutter InheritedWidget / InheritedElement 的一层工程化封装:负责创建对象、缓存对象、监听对象变化、通知依赖它的 Widget 重建。
核心实现
Provider<T> 最终会构建一个 _InheritedProviderScope<T?>,它继承自 InheritedWidget。
真正干活的是对应的 _InheritedProviderScopeElement<T>,它继承自 InheritedElement,负责保存 value、管理依赖、触发通知。
context.watch<T>() / Provider.of<T>(context) 会沿着 Element 树查找最近的 _InheritedProviderScope<T?>。
context.read<T>() 只读取,不建立依赖,所以后续变化不会导致当前 Widget 重建。
context.select<T, R>() 会只订阅从 T 中选出的某个字段,只有选中值变化时才重建。
可以把它理解为:
Provider<T>
-> InheritedProvider<T>
-> InheritedProviderScope<T>
-> InheritedProviderScopeElement<T>
Provider 的生命周期
Provider 常见有两种构造方式:
Provider(
create: (
) => Service(),
dispose: (
, service) => service.dispose(),
child: App(),
)
和:
Provider.value(
value: existingService,
child: App(),
)
源码层面差异很重要:
create:Provider 拥有对象生命周期,会在需要时创建,并在卸载时调用 dispose。
.value:Provider 不拥有对象生命周期,只是把已有对象暴露出去,通常不会替你销毁。
默认是 lazy: true,也就是第一次被读取时才调用 create。
如果设置 lazy: false,会在 Provider 插入树时立即创建。
这就是为什么官方一直建议:新建对象用 create,复用已有对象用 .value。
读取逻辑
典型使用:
final user = context.watch<User>();
final api = context.read<ApiService>();
final name = context.select<User, String>((user) => user.name);
大致对应源码逻辑:
static T of<T>(BuildContext context, {bool listen = true}) {
final inheritedElement = context.getElementForInheritedWidgetOfExactType<
_InheritedProviderScope<T?>>();

if (listen) {
context.dependOnInheritedWidgetOfExactType<_InheritedProviderScope<T?>>();
}

return inheritedElement.value;
}
这里关键点是:
getElementForInheritedWidgetOfExactType:只查找,不订阅。
dependOnInheritedWidgetOfExactType:查找并建立依赖。
listen: true 时,Provider 更新会让当前 Widget 重建。
listen: false 时,只拿值,不参与重建。
所以:
context.watch<T>()
约等于:
Provider.of<T>(context)
而:
context.read<T>()
约等于:
Provider.of<T>(context, listen: false)
ChangeNotifierProvider
ChangeNotifierProvider<T extends ChangeNotifier> 是 Provider 最常用的一层封装。
它的核心逻辑是:
ListenableProvider(
startListening: _startListening,
)
_startListening 大致做的事是:
value.addListener(markNeedsNotifyDependents);
return () => value.removeListener(markNeedsNotifyDependents);
所以调用链是:
model.notifyListeners()
-> ChangeNotifier 的 listener 被触发
-> Provider 标记依赖者需要通知
-> 依赖了该 Provider 的 Widget 重建
例如:
class CounterModel extends ChangeNotifier {
int count = 0;

void increment() {
count++;
notifyListeners();
}
}
使用:
ChangeNotifierProvider(
create: (_) => CounterModel(),
child: const CounterPage(),
)
读取:
class CounterPage extends StatelessWidget {
const CounterPage({super.key});

@override
Widget build(BuildContext context) {
final count = context.watch<CounterModel>().count;

return Scaffold(
  body: Center(child: Text('$count')),
  floatingActionButton: FloatingActionButton(
    onPressed: () => context.read<CounterModel>().increment(),
    child: const Icon(Icons.add),
  ),
);

}
}
这里 Text 会因为 watch 重建;按钮回调里的 read 不会让按钮本身订阅变化。
Consumer 与 Selector
Consumer<T> 只是把 Provider.of<T>(context) 包成 Widget,方便缩小重建范围:
Consumer<CounterModel>(
builder: (, model, child) {
return Text('${model.count}');
},
)
Selector 更进一步,只监听局部数据:
Selector<UserModel, String>(
selector: (
, model) => model.name,
builder: (, name, ) => Text(name),
)
源码思想是:保存上一次 selector 的返回值,用相等性比较判断是否需要重建。适合避免“大对象里任意字段变化导致整个区域重建”。
MultiProvider
MultiProvider 没有新的状态管理能力,只是把嵌套 Provider 展平:
MultiProvider(
providers: [
Provider(create: (
) => ApiService()),
ChangeNotifierProvider(create: (
) => UserModel()),
],
child: App(),
)
等价于:
Provider(
create: (
) => ApiService(),
child: ChangeNotifierProvider(
create: () => UserModel(),
child: App(),
),
)
ProxyProvider
ProxyProvider 用来声明依赖关系。比如 Repository 依赖 ApiService:
ProxyProvider<ApiService, Repository>(
update: (
, api, previous) => Repository(api),
)
当上游 ApiService 变化时,update 会重新执行。源码层面它主要是在 Provider 更新阶段读取依赖,然后生成新的 value。
常见最佳实践
新建对象:用 create,不要用 .value。
已有对象:用 .value,不要交给 Provider 销毁。
UI 展示数据:用 watch / Consumer / Selector。
事件回调:用 read。
大模型局部字段:优先 select 或 Selector。
不要在 build 中直接触发会修改状态的方法,否则容易出现构建期间更新状态的问题。
ChangeNotifier 适合中小型状态;状态复杂时可以拆多个 Notifier,避免一个 notifyListeners() 牵动太多 UI。
Provider 的源码核心就是:用 InheritedWidget 提供对象,用 InheritedElement 建立依赖关系,用 ChangeNotifier.addListener 把模型变化转成 Widget 重建。它真正解决的是“对象如何放进 Widget 树、如何读取、如何订阅、如何释放”。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容