前语

前面简略讲了 InheritedWidget,其为咱们处理了根底数据同享问题,但跨页面状况管理很不便利,比较适合静态数据, 因而 provider 应运而生

provider 其依据 InheritedWidget,数据同享问题必定ok,与此同时,还便利了咱们跨多个页面进行数据交互,运用灵敏,且关于 build 调用比较频繁的 Widget,其也有优化措施,能够削减调用,能够说非常优异,而且其依据 InheritedWidget,因而其也是最接近 flutter 风格的一款跨组件的状况处理方案了

ps: 顺便提一下,getx 一把嗦一时爽,一直嗦一直爽,同时其也面对着另一个问题,开发风格现已脱离了原本的 flutter 风格,初级开发很简单对其过度依靠,这样对 flutter 的了解,以及后续组件生态会存在一个断层,毕竟很多开发者组件是要削减依靠的,不会依据 getx 进行开发,因而 provider 亦是一个不错的持续处理方案(这我两个都看了并测验,让我感觉更接近flutter的便是他了,因而写文章以及后续运用也会是他,还有别的几个个人感觉现已被过渡过去了,能够放弃)

provider地址

demo地址(provider文件夹):里边也有 inheritedWidget 的事例,能够运转看看作用

本篇文章参考自 provider文档,仅仅从运用者的角度,去看看怎么运用的(不评论冗余的源码原理),看了这篇文章,基本上保证能在应用中灵敏处理各种问题了

provider

provider 作为一个跨组件的状况处理方案,下面会讲解,运用它是怎么处理咱们的组件状况的

ps:后边看到的参数 _、__之类的下划线,其实他们也是参数姓名,只不过用不到所以就这么命名了,也能够运用原来的姓名,

ChangeNotifier

ChangeNotifier咱们传递数据时需求用到的一个类,咱们需求承继他,在里边定义咱们的数据,然后告诉给其他组件咱们的更改便能够了(provider会自动监听更新,并告诉需求更新的当地)

class UserInfo {
  String? username;
  String? sex;
  int? age;
  UserInfo();
}
//定义咱们的目标,承继自 ChangeNotifier
//一般一个告诉定义一个展开的目标(例如:仅代表用户,里边是展开的用户信息)
//实践依据状况在里边能够定义多个同类别目标,这样能够防止过多的告诉类和provider出现,且告诉代码少了
class GlobalNotifier extends ChangeNotifier {
  //咱们的userInfo或许默许不存在,咱们能够在恰当的当地给赋值,这儿直接赋值,便利后边直接更改
  UserInfo? _userInfo = UserInfo();
  //留给外部拜访,因为 set 操作要进行告诉,就增加新的 set 和 get 便利一致调度
  UserInfo? get user => _userInfo;
  set user(UserInfo? newValue) {
    _userInfo = newValue;
    notifyListeners();
  }
}

根目录参加大局 Provider

咱们在 MaterialApp 增加一个大局的 provider,绑定咱们的 GlobalNotifier即可,一个ChangeNotifier需求一个Provider ,多个ChangeNotifier需求多个Provider(后边会简略介绍)

Provider为单纯的只读 provider,无法收到状况更新,只能作为静态运用,运用的比较少,乱用会报错(不引荐,除非就寄存静态数据)

ChangeNotifierProvider 是支撑告诉(支撑读写)的 provider,不仅能够读取内容,还能够接纳到更新的状况信息(引荐运用)

//在根目录的咱们参加大局Provider便是用这个就行了,不运用 ChangeNotifierProvider.value
//因为根目录也不便利更改,后边看了事例就理解了为什么了
ChangeNotifierProvider(
  create: (_) {
    return GlobalNotifier();
  },
  child: MaterialApp(
    title: 'Flutter Demo',
    theme: ThemeData(
      primarySwatch: Colors.blue,
    ),
    home: const MyHomePage(title: 'Flutter Demo Home Page'),
  ),
);

获取 provider 状况原始办法(不引荐)

仿照 InheritedWidget 的读写办法,完美显现和更新,如下所示,但不引荐这么运用

@override
Widget build(BuildContext context) {
  //最原始的读取参数的办法,不引荐这么用
  final global = Provider.of<GlobalNotifier>(context);
  return Padding(
    padding: const EdgeInsets.all(10),
    child: Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
        Text("测验内容1:${global.user?.username}"),
        TextButton(
          onPressed: () {
            final user = global.user;
            if (user != null) {
              user.username = "test1";
            }
            global.user = user;
          },
          child: const Text("更新"),
        ),
      ],
    ),
  );
}

获取 provider 的读写扩展办法 (引荐)

Provider给咱们的 BuildContext 扩展了读写之类的办法,

watch 获取的参数同时支撑读写,不仅能默许展示内容,也能及时接纳更新新内容

read获取的变量能读取到信息,但无法接纳更改的信息

@override
Widget build(BuildContext context) {
  //引荐下面这么用,文档引荐,这么运用一定没错
  //运用watch能够正常接纳到更新的音讯
  final global = context.watch<GlobalNotifier>();
  //假如监听依靠的或许会不存在,那么能够运用 ?
  // final global = context.watch<GlobalNotifier?>();
  // 运用read无法监听到更新的最新告诉,但运用其更新数据,其他watch的当地仍然能够接纳更改告诉
  // final global = context.read<GlabalNotifier>();
  return Padding(
    padding: const EdgeInsets.all(10),
    child: Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
        Column(
          children: [
            Text("测验内容2(watch):${global.user?.username}"),
            Text("测验内容2(read):${context.read<GlobalNotifier>().user?.username}"),
          ],
        ),
        TextButton(
          onPressed: () {
            //因为更新的是一个目标的一个特点,先获取后更新即可,假如是分散的特点,直接更新即可
            final user = global.user;
            if (user != null) {
              user.username = "test2";
            }
            global.user = user;
          },
          child: const Text("更新"),
        ),
      ],
    ),
  );
}

provider 定向更新部分参数,优化功能(引荐)

provider更新时,会告诉对应 Notifier的所有参数,这时咱们没有更新的办法也会触发 build 了,削减不必要的烘托,此时咱们能够经过 select 定向获取某一个参数特点,防止不必要的烘托

如下所示,咱们分别过滤出咱们的 username 和 age,能够削减其他特点更新的干扰到这边从头执行 build

@override
Widget build(BuildContext context) {
  //过滤其他更改,只更新对应特点,防止从头build widget
  final username = context.select((GlobalNotifier e) => e.user?.username);
  final age = context.select((GlobalNotifier e) => e.user?.age);
  return Padding(
    padding: const EdgeInsets.all(10),
    child: Column(
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            Text("测验3(单特点name):$username"),
            TextButton(
              onPressed: () {
                final global = context.read<GlobalNotifier>();
                if (global.user != null) {
                  global.user!.username = "test3";
                }
                global.user = global.user;
              },
              child: const Text("更新"),
            ),
          ],
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            Text("测验3(单特点age):$age"),
            TextButton(
              onPressed: () {
                final global = context.read<GlobalNotifier>();
                if (global.user != null) {
                  global.user!.age = 20;
                }
                global.user = global.user;
              },
              child: const Text("更新"),
            ),
          ],
        ),
      ],
    ),
  );
}

运用 Consumer 优化部分组件功能

Consumer 为一个功能优化部件,页面不大其实也没必要运用,其不仅能够接纳到对应告诉的改换,并及时反馈到组件上,且能保证该组件外部不会从头build,只更新内部包裹的组件,因而也算是不错的功能优化手段了

相比较 select 其更加优异,但不抵触,能够依据必要两个都带,例如:Consumer里边包裹的组件里边依据状况再运用 select,也算是另一种极限优化了

此外,或许还会因为 ConsumerConsumer2、…、Consumer6怎么这么多,是干什么的

先看看 Consumerbuilder 就知道了,里边共有三个参数,第一个是 context,最后一个是 child,中心的便是咱们泛型对应的值,那么 Consumer2 便是中心有两个,Consumer6 便是中心 6 个,这下理解了吧(ps:作者也是被泛型给耽误了,数组也是能够的哈)

Widget Function(
  BuildContext context,
  T value,
  Widget? child,
)

Consumer 的应用事例如下所示

@override
Widget build(BuildContext context) {
  return Padding(
    padding: const EdgeInsets.all(10),
    child: Column(
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            //默许监听变化,跟watch相同
            //context value1 child
            Consumer<GlobalNotifier>(builder: (_, global, __) {
              return Text("测验4(优化部分Consumer):${global.user?.username}");
            }),
            //同理,最多到 Consumer2,即6个参数
            //context value1 value2 child
            // Consumer2<GlobalNotifier, GlobalNotifier2>(builder: (_, global, global2, __) {
            //   return Text("测验4(优化部分控件改写Consumer):${global.user?.username}");
            // }),
            TextButton(
              onPressed: () {
                final global = context.read<GlobalNotifier>();
                if (global.user != null) {
                  global.user!.username = "测验4";
                }
                global.user = global.user;
              },
              child: const Text("更新"),
            ),
          ],
        ),
      ],
    ),
  );
}

部分 Provider(部分某种意义上或许是某个模块的大局)

介绍部分 Provider 的时分,咱们顺道也把 Provide.value 介绍了

前面介绍了,一个 Notifier 对应一个 Provider,因而一个项目中一定会有多个 Provider(这儿边多个Provider 仅仅供给多个数据,运用上仍是相同的)

除了前面的大局,其他大多数场景都是在部分了,部分 Provider全部的运用上相同,唯一的不同便是,Provider 依据泛型获取对应的 Notifier 时,会获取最部分的那个,也便是离他最近的一个那个 Notifier

举个例子:在根部 MaterialApp 定义了一个 UserNotifier,咱们的个人页面也定义了一个 UserNotifier,此时在个人模块获取到的 UserNotifier 便是个人页面定义声明的部分 UserNotifier了,即每次进入到个人模块,用户都会运用部分的个人用户模块,假如假定大局给的是一个含糊的客户用户,那么进入个人模块获取便是一个精准的个人模块信息了

与此同时,某个模块部分 Provider 在另一个部分 Provider面前,或许就相当于当时模块大局 Provider了,就跟临时变量的作用域优先级似的,了解了这个联系,就简单灵敏运用 Provider

场景一、假定下面是咱们的会员用户模块,每日进入咱们都会从头初始化咱们的会员参数,因而里边的 Notifier 参数相当于运用了一个全新的用户参数(假如同名的话)

//内外同时运用 同一个类型 value 时,会获取离得最近的 Provider 供给的内容
//不想运用前面的 provider 的话,能够直接回来child试试作用,信任马上会了解
//前面main里边也是这个global类型,这儿也是,会获取到最近的部分 value
ChangeNotifierProvider(
    create: (_) => GlobalNotifier(), 
    child: child,
);

场景二、仍是假定会员模块,咱们的会员模块会依据依据接口回来的参数决定 Notifier 状况,且当时页面随时或许更新 Notifier,因而为了便利 更新 Notifier ,咱们能够运用 ChangeNotifierProvider.value,以便利外部随时更新 Notifier,从而推进内部组件更新

//外部声明一个 notifier
GlobalNotifier? notifier;
//内部运用,就不贴出函数了
//假如value由外部传入更新,最好运用 ChangeNotifierProvider.value 形式
//这样内外会运用同一个 value,更新时会得到相同的作用
return ChangeNotifierProvider.value(
    value: notifier,
    child: child,
);

多个Provider的写法

前面提到了一个 Notifier 对应一个 Provider,假如需求多个参数,那么就需求多个 Provider了,此时运用嵌套的办法,假如数量比较多会堕入嵌套阴间,如下所示

//从上面也能够看出,咱们引用中或许运用了多个 provider,供给多个数据
//假如大局数据比较多的话,或许会存在这种状况
test1() {
  return ChangeNotifierProvider(
    create: (_) => GlobalNotifier2(),
    child: ChangeNotifierProvider(
      create: (_) => GlobalNotifier2(),
      child: ChangeNotifierProvider(
        create: (_) => GlobalNotifier2(),
        child: const Text(''),
      ),
    ),
  );
}

provider 推出了 MultiProvider,经过传递数组的办法来对接多个 Provider,来防止回调阴间

//经过数组代替嵌套,结果是相同的,是不是看着稍微舒服点了
test2() {
  return MultiProvider(
    providers: [
      ChangeNotifierProvider(
        create: (_) => GlobalNotifier2(),
      ),
      ChangeNotifierProvider(
        create: (_) => GlobalNotifier2(),
      ),
      ChangeNotifierProvider(
        create: (_) => GlobalNotifier2(),
      ),
    ],
    child: const Text(''),
  );
}

最后

运用代码就介绍这么多,实践上就现已完全够用了,能够下载 demo 运转试试,假如想深化了解,外面应该也有不少介绍原理的,原理上也没那么麻烦,能够多看看