书接上文
在Flutter状态管理:Provider4 入门教程(一)中,我们对状态管理以及Provider有了初步的了解,也学习了ChangeNotifierProvider以及Consumer的使用,但由于时间有限,讲到Consumer就关卖了个关子,连广告都忘打了,现在我们正式书接上文,从聊聊Consumer开始。
怎么还是Consumer
Consumer2-6
如果有细心的朋友在使用Consumer时可能会发现,有个几个神奇的类:
Consumer2Consumer3Consumer4Consumer5Consumer6
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>可以接收A和B两种类型的数据,从它的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发生变化时,BarWidget和FooWidget都会进行重绘。
但理想情况中,应该只有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系列的第二篇,内容依然很简单,而我又要说时间有限了。
未完待续。。。 期待不期待你说了算。