provider 跨组件状态管理


provider 跨组件状态管理

Provider 包是由 Remi Rousselet 创建旨在尽可能快速地处理状态。在 Provider 中,小部件会监听状态的变化,并在收到通知后立即更新。

因此,当有状态改变时,而不是重建整个 widget 树,只改变受影响的 widget,从而减少工作量并使应用程序运行得更快更流畅。

一、原理

Model变化后会自动通知ChangeNotifierProvider(订阅者),ChangeNotifierProvider内部会重新构建InheritedWidget,而依赖该InheritedWidget的子孙Widget就会更新。

原有普遍方式:通过参数传递数据,并setState实时更新组建;

使用Provider益处:

    1. 业务代码更关注数据,只要更新Model,则UI会自动更新,而不用在状态改变后再去手动调用setState()来显式更新页面。

    2. 数据改变的消息传递被屏蔽了,我们无需手动去处理状态改变事件的发布和订阅了,这一切都被封装在Provider中了。这真的很棒,帮我们省掉了大量的工作!

    3. 在大型复杂应用中,尤其是需要全局共享的状态非常多时,使用Provider将会大大简化我们的代码逻辑,降低出错的概率,提高开发效率。

二、状态管理方式

1、Provider 方式

provider 不需要被监听,有的常量或者方法,根本不需要“牵一发而动全身”,也就是说他们不会被要求随着变动而变动,这样的需求使用Provider;

最基本的状态管理方式,以一个参数方式绑定和展示;

1) 绑定单条数据

Provider 可在需要的 Widget 处进行数据绑定;

基本单条数据绑定:

当我们确定绑定的数据类型时,建议绑定时添加数据类型,如:Provider.value( value: '', child:);不确定类型Provider.value( value: '', child:)

Column(
    children: [
        Provider.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(context));
  }
}

 3)绑定多条数据

 Column(
  children: [
    /// 单条传值
    Provider.value(
        value: 'Provider 基础传值', child: const BaseValueWidget()),

    /// 多条传值 UserModel实体
    //嵌套绑定
    Provider.value(
        value: UserModel('嵌套绑定', 18),
        child: Provider.value(
            value: 20,
            child: Provider.value(
                value: false, child: const NestingWidget()))),
    // 聚合方式 推荐
    MultiProvider(providers: [
      Provider.value(value: UserModel('聚合方式', 10)),
      Provider.value(value: 11),
      Provider.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(context)} | ${Provider.of(context)} | ${Provider.of(context).name} |${Provider.of(context).name = 'Hello World!'}',
        style: const TextStyle(color: Colors.redAccent));
  }
}
Provider 绑定数据类型比较灵活,并非只是基本数据类型,这里定义了一个 UserModel 类,可正常状态管理;获取 name 后重新设置 name 之后获取的 UserModel 为最新的数据;

4)作用域

获取绑定数据的范围是在绑定数据的子 Widget 中;

2、ChangeNotifierProvider 方式

ChangeNotifierProvider 它会随着某些数据改变而被通知更新,也就是说,比如这个 Model 被用在多个 page,那么当其中一处被改变时,他就应该告诉其他的地方,改更新了,这样的需求就使用ChangeNotifierProvider;

通过调用 ChangeNotifier.notifyListenersChangeNotifier 进行监听,将其公开给它的子 Widget 并重建依赖项;

1) 绑定数据

ChangeNotifierProvider 绑定数据有两种方式:
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.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(context).name);

相对于 ProviderChangeNotifierProvider 方式更加灵活,可以通过重写 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(context).name),
        InkWell(
          child: const Text('点击更新name'),
          onTap: () {
            context.read().updateName('name');
          },
        ),
      ],
    );
  }
}

3、ChangeNotifierProxyProvider

ChangeNotifierProxyProvider 它不仅要像ChangeNotifierProvider一样,通知更新,还要协调 Model 与 Model 之间的更新,比如一个 ModelA 依赖另一个 ModelB,ModelB 更新,他就要让依赖它的 ModelA 也随之更新,这就是使用ChangeNotifierProxyProvider; https://pub.flutter-io.cn/documentation/provider/latest/provider/ChangeNotifierProxyProvider-class.html
ChangeNotifierProvider(
  create: (context) {
    return MyChangeNotifier(
      myModel: Provider.of(context, listen: false),
    );
  },
  child: ...
)

三、有选择地更新状态

该Consumer控件只允许子控件,而不在 widget 树影响其他部件重建,小部件进行更新。

我们通过Text用 a 包装两个小部件ColumnbuilderConsumer小部件公开的函数处返回它来实现这一点:

 
Consumer(
  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,
          ),
        ),
      ],
    );
  },
)