书接上文
在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>
可以接收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
系列的第二篇,内容依然很简单,而我又要说时间有限了。
未完待续。。。 期待不期待你说了算。