provider 跨组件状态管理
Provider 包是由 Remi Rousselet 创建旨在尽可能快速地处理状态。在 Provider 中,小部件会监听状态的变化,并在收到通知后立即更新。
因此,当有状态改变时,而不是重建整个 widget 树,只改变受影响的 widget,从而减少工作量并使应用程序运行得更快更流畅。
一、原理
Model变化后会自动通知ChangeNotifierProvider
(订阅者),ChangeNotifierProvider
内部会重新构建InheritedWidget
,而依赖该InheritedWidget
的子孙Widget就会更新。
原有普遍方式:通过参数传递数据,并setState实时更新组建;
使用Provider益处:
-
业务代码更关注数据,只要更新Model,则UI会自动更新,而不用在状态改变后再去手动调用
setState()
来显式更新页面。 -
数据改变的消息传递被屏蔽了,我们无需手动去处理状态改变事件的发布和订阅了,这一切都被封装在Provider中了。这真的很棒,帮我们省掉了大量的工作!
-
在大型复杂应用中,尤其是需要全局共享的状态非常多时,使用Provider将会大大简化我们的代码逻辑,降低出错的概率,提高开发效率。
-
二、状态管理方式
1、Provider 方式
provider 不需要被监听,有的常量或者方法,根本不需要“牵一发而动全身”,也就是说他们不会被要求随着变动而变动,这样的需求使用Provider;
最基本的状态管理方式,以一个参数方式绑定和展示;
1) 绑定单条数据
Provider 可在需要的 Widget 处进行数据绑定;
基本单条数据绑定:
当我们确定绑定的数据类型时,建议绑定时添加数据类型,如:Provider<String>.value( value: '', child:);不确定类型Provider.value( value: '', child:);
Column( children: [ Provider<String>.value( value: 'Provider 基础传值', child: const BaseValueWidget()), ], )
2)获取数据
class BaseValueWidget extends StatelessWidget { const BaseValueWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Text(Provider.of<String>(context)); } }
3)绑定多条数据
Column( children: [ /// 单条传值 Provider<String>.value( value: 'Provider 基础传值', child: const BaseValueWidget()), /// 多条传值 UserModel实体 //嵌套绑定 Provider<UserModel>.value( value: UserModel('嵌套绑定', 18), child: Provider<int>.value( value: 20, child: Provider<bool>.value( value: false, child: const NestingWidget()))), // 聚合方式 推荐 MultiProvider(providers: [ Provider<UserModel>.value(value: UserModel('聚合方式', 10)), Provider<int>.value(value: 11), Provider<bool>.value(value: false) ], child: const NestingWidget()), ], )
class UserModel { String name; int age; UserModel(this.name, this.age); }
获取数据
class NestingWidget extends StatelessWidget { const NestingWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Text( 'Provider: ' '${Provider.of<int>(context)} | ${Provider.of<bool>(context)} | ${Provider.of<UserModel>(context).name} |${Provider.of<UserModel>(context).name = 'Hello World!'}', style: const TextStyle(color: Colors.redAccent)); } }
4)作用域
2、ChangeNotifierProvider 方式
ChangeNotifierProvider 它会随着某些数据改变而被通知更新,也就是说,比如这个 Model 被用在多个 page,那么当其中一处被改变时,他就应该告诉其他的地方,改更新了,这样的需求就使用ChangeNotifierProvider;
通过调用 ChangeNotifier.notifyListeners 对 ChangeNotifier 进行监听,将其公开给它的子 Widget 并重建依赖项;
1) 绑定数据
ChangeNotifierProvider({Key key, @required ValueBuilder builder, Widget child });通过构造器创建一个 ChangeNotifier,在 ChangeNotifierProvider 移除时自动处理;
ChangeNotifierProvider( create: (_) => PersonChangeNotifier('ChangeNotifier方式1', 11), child: const NotifierWidget(), ),
ChangeNotifierProvider.value({Key key, @required T notifier, Widget child }) 通过监听通知给子 Widget 并重建依赖项;
ChangeNotifierProvider<PersonChangeNotifier>.value( value: PersonChangeNotifier('ChangeNotifier方式2', 1), child: const NotifierWidget(), )
class PersonChangeNotifier with ChangeNotifier { String name; int age; PersonChangeNotifier(this.name, this.age); updateName(String name) { this.name = name; notifyListeners(); } }
2) 获取数据
获取数据的方式与直接使用 Provider 相似;
Text(Provider.of<PersonChangeNotifier>(context).name);
相对于 Provider,ChangeNotifierProvider 方式更加灵活,可以通过重写 get/set 方法来对状态管理进行修改和使用;
3)更新数据
class NotifierWidget extends StatelessWidget { const NotifierWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Column( children: [ Text(Provider.of<PersonChangeNotifier>(context).name), InkWell( child: const Text('点击更新name'), onTap: () { context.read<PersonChangeNotifier>().updateName('name'); }, ), ], ); } }
3、ChangeNotifierProxyProvider
ChangeNotifierProvider( create: (context) { return MyChangeNotifier( myModel: Provider.of<MyModel>(context, listen: false), ); }, child: ... )
三、有选择地更新状态
该Consumer控件只允许子控件,而不在 widget 树影响其他部件重建,小部件进行更新。
我们通过Text
用 a 包装两个小部件Column
并builder
在Consumer
小部件公开的函数处返回它来实现这一点:
Consumer<UserDetailsProvider>( builder: (context, provider, child) { return Column( children: [ Text( 'Hi ' + provider.name, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), Text( 'You are ' + provider.age.toString() + ' years old', style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w400, ), ), ], ); }, )