Flutter状态管理:Provider4 入门教程(二)

4,158 阅读5分钟

书接上文

Flutter状态管理:Provider4 入门教程(一)中,我们对状态管理以及Provider有了初步的了解,也学习了ChangeNotifierProvider以及Consumer的使用,但由于时间有限,讲到Consumer就关卖了个关子,连广告都忘打了,现在我们正式书接上文,从聊聊Consumer开始。

怎么还是Consumer

Consumer2-6

如果有细心的朋友在使用Consumer时可能会发现,有个几个神奇的类:

  • Consumer2
  • Consumer3
  • Consumer4
  • Consumer5
  • Consumer6

这是啥?难不成Consumer成精了?

其实并不是哦。这其实是Consumer大家族,一共六位成员,Consumer后面的数字代表了Consumer可接收的数据类数量。

天呐,原来是这个意思,那为什么没有Consumer1000?亲,这边建议您去问作者吧,我怕被打死呢。所以说,如果当真有这么个需求,还是自力更生吧。

说到这里,我们再简单地聊一下Consumer

整个Consumer家族使用了Builder模式,当Consumer收到了更新就会通知builder更新。而builder实际上就是一个Function,也可以说是一个lamda了。以Conumer为例,它的builder被定义为Widget (BuildContext context, T model, Widget child),一共接收三个参数,其中的T就是可以接收的数据类型,T其实就是Consumer<T>里的泛型T,所以说使用Consumer时一定要提供泛型。以前文的代码为例,Consumer<MyChangeNotifier>就是说这个Consumer接收MyChangeNotifier实例。以此类推,Consumer2<A,B>可以接收AB两种类型的数据,从它的builder的定义中就可以看出来:Widget Function(BuildContext context, A value, B value2, Widget child)

builder中的child是用来构建那些与数据模型无关的部分,在多次运行builder中,child也不会进行重绘。

按道理来说,现在应该来说说Consumer3了,但是考虑到他们其实都大同小异了,就不凑字数了,具体大家可以参看源码。

为什么推荐使用Consumer

前文我说过:

Consumer本身没有魔法,也没有什么花里胡哨的实现。只不过是在一个新的控件中使用Provider.of,然后将这个控件的build方法委托给参数里的builder。这个builder会被调用多次。就是这么简单。

但是我也说过Consumer可以为我们提供性能上的优化,因为Consumer可以为我们提供更细小的颗粒化重绘。

我们要知道当使用Provider.of时,除非我们设置了listen:false,否则只要Provider.of中接收的数据发生了变化,与Provider.of中的BuildeContext相关的控件都会进行重新构建。这当然是我们期望的行为,但有时候这可能会引起不必要的过多的重绘。

我们看一个例子:

 @override
 Widget build(BuildContext context) {
   return FooWidget(
     child: BarWidget(
       bar: Provider.of<Bar>(context),
     ),
   );
 }

上面的代码中,只有BarWidget依赖Provider.of中返回的数据。但是当Bar发生变化时,BarWidgetFooWidget都会进行重绘。

但理想情况中,应该只有BarWidget进行重绘。一种解决方案就是使用Consumer

为了实现我们刚说的方法,我们将对用Consumer对依赖Provider的控件进行包裹。

 @override
 Widget build(BuildContext context) {
   return FooWidget(
     child: Consumer<Bar>(
       builder: (_, bar, __) => BarWidget(bar: bar),
     ),
   );
 }

现在,如果Bar要进行更新,只有BarWidget才会进行重绘。 但是如果FooWidget依赖了一个provider了怎么办? 比如说:

 @override
 Widget build(BuildContext context) {
   return FooWidget(
     foo: Provider.of<Foo>(context),
     child: BarWidget(),
   );
 }

还记得我上面说的child吗?对,就是它,上代码:

 @override
 Widget build(BuildContext context) {
   return Consumer<Foo>(
     builder: (_, foo, child) => FooWidget(foo: foo, child: child),
     child: BarWidget(),
   );
 }

这个例子中,BarWidget是在builder之外进行重绘的。然后BarWidget实例作为最一个参数传递给给builder

这意味着builder会被反复调用时,Consumer并不会创建BarWidget新实例。这会让Flutter知道不必重新绘制BarWidget。所以通过这么样的一个写法,当Foo有更新时,只有FooWidget才会进行重绘。

有兴趣的朋友,可以实现上述示例代码,然后在build()中加几个print,然后验证一下说的对不对。

Consumer小结

总得来说,Consumer有两个主要用处:

  • 当我们的BuildContext中不存在指定的Provider时,Consumer允许我们从Provider中的获取数据。
  • 提升性能

所以我还是很建议使用Consumer的,毕竟可以带来性能提升。Flutter诚然会我们做很多优化,但很多并不代表全部,性能的提升更多的是依靠我们自己的实现方式。

MultiProvider

我们之前一直在讲一个页面一个Provider的情型,但实际上很多上了规模的应用,它可能不止一个Provider,搞个几十个provider也是可能的,这时我们会想嵌套大法:

Provider<Something>(
  create: (_) => Something(),
  child: Provider<SomethingElse>(
    create: (_) => SomethingElse(),
    child: Provider<AnotherThing>(
      create: (_) => AnotherThing(),
      child: someWidget,
    ),
  ),
),

一句以F开头以U结尾的话是不是又要蹦出来?别急,作者很体贴地设计了MultiProvider

MultiProvider(
  providers: [
    Provider<Something>(create: (_) => Something()),
    Provider<SomethingElse>(create: (_) => SomethingElse()),
    Provider<AnotherThing>(create: (_) => AnotherThing()),
  ],
  child: someWidget,
)

当然了,这两段代码是完全等价的,所以MultiProvider也没有什么黑魔法,只是不让代码看起不那么ugly

笔记:Consumer也可以在MultiProvider使用。但Consumer必须返回它在控件树中创建的child,也就是builder中的builder

MultiProvider(
  providers: [
    Provider(create: (_) => Foo()),
    Consumer<Foo>(
      builder: (context, foo, child) =>
        Provider.value(value: foo.bar, child: child),
    )
  ],
);

我有个问题

上面我们说到了MultiProvider,忽然间脑袋灵光一现,有个问题想问:

  • 可以在一个BuildContext中获取具有相同数据类型的多个provider吗?

用代码说就是这样式的:

Provider<String>(
  create: (_) => '中国',
  child: Provider<String>(
    create: (_) => '大连',
    child: ...,
  ),
),

答案是否定的。当有多个具体相同数据类型的provider时,一个控件只能获取到一个:离他最近的。

所以嘞,我们必须明确地指出他们的类型:

Provider<Country>(
  create: (_) => Country('中国'),
  child: Provider<City>(
    create: (_) => City('大连'),
    child: ...,
  ),
),

emmmm,看起来还不错。

欲知后事请听下回分解

作为Provider系列的第二篇,内容依然很简单,而我又要说时间有限了。

未完待续。。。 期待不期待你说了算。