本文收录于
张风捷特烈
的公众号编程之王
: 文章内存地址f-s-p-01
,
如何获取更多知识干粮
,详见<<编程之王食用规范1.0>>
编程
Flutter的状态管理三足鼎立,明媒正室当
Provider
莫属,可谓刘备级别的大佬,名正言顺。做为一个喜欢偷懒的人,能省则省。都知道Provider有一把梭,打遍天下无敌手。不过刷这两招,可要悠着点,不然代价就是性能。性能优化
Provider.of<XXX>(context).数据
Provider.of<XXX>(context).方法
复制代码
页面以下,第一个界面是四个色块,点击蓝色字时跳到紫色界面
这里进行了五次操做:状态同步实现,貌似表面上
完美无瑕,并且一把梭就OK了,也很方便,BUT!!!----往下翻。bash
1:打开界面
2:点击按钮,+1
3:点击蓝块文字,跳转界面
4:点击紫块,触发方法
5:返回
复制代码
class CountState with ChangeNotifier {
int _count = 0;
get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
void main() {
final count = CountState();
runApp(MultiProvider(
providers: [ChangeNotifierProvider.value(value: count)],
child: MyApp(),
));
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primaryColor: Colors.blue,
),
home: new HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Wrap(
spacing: 10,
runSpacing: 10,
children: <Widget>[
RedBox(),YellowBox(),BlueBox(),GreenBox(),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Provider.of<CountState>(context).increment();
},
child: Icon(Icons.add),
)
);
}
}
class RedBox extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("---------RedBox---------build---------");
return Container(
color: Colors.red,
width: 150,
height: 150,
alignment: Alignment.center,
child: Text("Red:${Provider.of<CountState>(context).count}",
style: TextStyle(fontSize: 20),),
);
}
}
class YellowBox extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("---------YellowBox---------build---------");
return Container(
color: Colors.yellow,
width: 150,
height: 150,
alignment: Alignment.center,
child: Text("Yellow:${Provider.of<CountState>(context).count}",
style: TextStyle(fontSize: 20)),
);
}
}
class BlueBox extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("---------BlueBox---------build---------");
return Container(
color: Colors.blue,
width: 150,
height: 150,
alignment: Alignment.center,
child: InkWell(
onTap:() {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => NextPage()));
},
child: Text("Blue:${Provider.of<CountState>(context).count}",
style: TextStyle(fontSize: 20)),
),
);
}
}
class GreenBox extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("---------GreenBox---------build---------");
return Container(
color: Colors.green,
width: 150,
height: 150,
alignment: Alignment.center,
child: Text("GreenBox:${Provider.of<CountState>(context).count}",
style: TextStyle(fontSize: 20)),
);
}
}
class NextPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("---------NextPage---------build---------");
return Scaffold(
body: Center(
child: InkWell(
onTap: (){
Provider.of<CountState>(context).increment();
},
child: Container(
color: Colors.purple,
width: 150,
height: 150,
alignment: Alignment.center,
child: Text("NextPage:${Provider.of<CountState>(context).count}",
style: TextStyle(fontSize: 20))),
)),
);
}
}
复制代码
打印的日志让我一身冷汗。不知道有多少人为了方便滥用这一把梭。
可见这里在跳转时、五个组件所有触发build,在第二个页面(紫块)执行方法时竟五个组件所有触发build
,其中四个都是不可见的组件,build何用?微信
---->[1.打开时]----
I/flutter (24913): ---------RedBox---------build---------
I/flutter (24913): ---------YellowBox---------build---------
I/flutter (24913): ---------BlueBox---------build---------
I/flutter (24913): ---------GreenBox---------build---------
---->[2.点击+号,触发方法]----
I/flutter (24913): ---------RedBox---------build---------
I/flutter (24913): ---------YellowBox---------build---------
I/flutter (24913): ---------BlueBox---------build---------
I/flutter (24913): ---------GreenBox---------build---------
---->[3.点击蓝块文字,跳转界面]----
I/flutter (24913): ---------NextPage---------build---------
I/flutter (24913): ---------RedBox---------build---------
I/flutter (24913): ---------YellowBox---------build---------
I/flutter (24913): ---------BlueBox---------build---------
I/flutter (24913): ---------GreenBox---------build---------
---->[4.点击紫块,触发方法]----
I/flutter (24913): ---------NextPage---------build---------
I/flutter (24913): ---------GreenBox---------build---------
I/flutter (24913): ---------BlueBox---------build---------
I/flutter (24913): ---------YellowBox---------build---------
I/flutter (24913): ---------RedBox---------build---------
---->[5.返回]----
I/flutter (24913): ---------RedBox---------build---------
I/flutter (24913): ---------YellowBox---------build---------
I/flutter (24913): ---------BlueBox---------build---------
I/flutter (24913): ---------GreenBox---------build---------
复制代码
这时候使用Consumer包裹须要更新的节点。
将四个色块处理
框架
class HomePage extends StatelessWidget {
//英雄所见...
floatingActionButton://使用Consumer包裹
Consumer<CountState>(builder: (ctx,state,child)=>FloatingActionButton(
onPressed: () {
state.increment();//使用其内的state执行方法
},
child: Icon(Icons.add),
))
);
}
}
class RedBox extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("---------RedBox---------build---------");
return Container(
color: Colors.red,
width: 150,
height: 150,
alignment: Alignment.center,
child: Consumer<CountState>(builder: (ctx,state,child)=>
Text("Red:${state.count}",
style: TextStyle(fontSize: 20),)),
);
}
}
//其余三个处理相似,略...
复制代码
如今再来看看打印结果,什么是世界瞬间清净了许多。
Consumer能够指定小块的局部消费,避免总体的的所有刷新less
---->[1.打开时]----
I/flutter (24913): ---------RedBox---------build---------
I/flutter (24913): ---------YellowBox---------build---------
I/flutter (24913): ---------BlueBox---------build---------
I/flutter (24913): ---------GreenBox---------build---------
---->[2.点击+号,触发方法]----
无打印信息
---->[3.点击蓝块文字,跳转界面]----
I/flutter (26468): ---------NextPage---------build---------
---->[4.点击紫块,触发方法]----
I/flutter (26468): ---------NextPage---------build---------
---->[5.返回]----
无打印信息
复制代码
你也许会说,乖乖,这么秀,都不用build了?但不吃饭是长不胖的...且静看下文。ide
源码第一句说的很清楚:
从先祖获取Provider<T>而后传递给builder出的组件
原本代代相承的传家宝直接经过Consumer隔代传送。不作那些花里胡哨的传递。性能
目的有2:
其一:当没有BuildContext时可使用Consumer
@override // ERROR:ProviderNotFoundError 由于该context中并无Provider
Widget build(BuildContext context) {
return ChangeNotifierProvider(
builder: (_) => Foo(),
child: Text(Provider.of<Foo>(context).value),
);
}
@override // OK
Widget build(BuildContext context) {
return ChangeNotifierProvider(
builder: (_) => Foo(),
child: Consumer<Foo>(
builder: (_, foo, __) => Text(foo.value),
},
);
}
其二:它经过更细粒度的重构来帮助性能优化。
复制代码
经过上面可知实际上是建立有构建组件的,只不过是局部构建
这样可让构建的粒度变细,天然避免了没必要要的过程,能够在Builder里打印来测试一下也就是说,构建的只是知识一个Text而非整个RedBox。测试
class RedBox extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("---------RedBox---------build---------");
return Container(
color: Colors.red,
width: 150,
height: 150,
alignment: Alignment.center,
child: Consumer<CountState>(builder: (ctx,state,child){
print("---------RedBox----Consumer-----build---------");
return Text("Red:${state.count}",
style: TextStyle(fontSize: 20),);
}),
);
}
}
复制代码
那他是如何实现的呢?一共也不到20行。它继承自StatelessWidget
能够看出有三个字段:key、child和、builder,其中builder是一个三参的方法 既然是StatelessWidget,build方法天然跑不了。可见该方法是由 builder方法全权负责的。T泛型就是状态模型,这里也是经过Provider.of<T>(context),
来拿到的。优化
class Consumer<T> extends StatelessWidget
implements SingleChildCloneableWidget {
Consumer({
Key key,
@required this.builder,
this.child,
}) : assert(builder != null),
super(key: key);
final Widget child;
final Widget Function(BuildContext context, T value, Widget child) builder;
@override
Widget build(BuildContext context) {
return builder(
context,
Provider.of<T>(context),
child,
);
}
@override
Consumer<T> cloneWithChild(Widget child) {
return Consumer(
key: key,
builder: builder,
child: child,
);
}
}
复制代码
那问题来了?传入的context是谁的BuildContext? 众所周知,每一个Widget都有属于本身的元素Element,在该Element进行mount的时候回将自身化做美丽的天使(Context)传入组件或State的build方法中来供你使用。这里分别在
顶层MyApp的build
、页面HomePage的build``红色的build
、红色Consumer内部
打上断点,来一窥这四个小天使的容颜。
因此BuildContext并非咱们想象中的,什么代代相传的东西。而是每一个Widget特有的存在,就像他们的基因同样,在每一个Widget里都是不一样的。因此咱们的问题很简单,Consumer做为一个Widget,它提供的context即是Consumer的context。下面看一下这几个小天使在界面的Element树上的位置。
再强调一下,Element是实现BuildContext抽象接口协议的具象类,Widget或State中Build传入的BuildContext都是各自的组件对应的Element。每一个Element都会记录它们的父亲,就像这样,按照一个BuildContext(即Element),你能够找到它的祖宗18代,应该是祖宗108代。虽然Widget伪树
很是简短,但Element树并不想你想象的那样。
也许你会好奇,最顶级的元总是谁?那咱们就偷瞄一下,谁是天使之王。
框架在开始是会建立一个 RenderObjectToWidgetElement,她即是一切美丽的根源。
紧接着即是Provider提供的MultiProvider ,咱们的MyApp还要后两辈。
Consumer何德何能,居然直接越过父亲? 遇事不决,量子力学,把哥的debug量子炮拿来
在点击时触发方法是打个断点,来走一波。
断点处: 3个
state.increment()
红色Consumer内部
buildScope方法
当第一次点击按钮时:
buildScope 中脏表元素 1 ,为按钮元素:RawMaterialButton(dirty)
接下来断点走到state.increment();开始触发通知更新
会走到buildScope,脏表数为3
ListenableProvider<CountState>(dirty, state: _DelegateWidgetState#ae650)
RawMaterialButton(dirty, state: _RawMaterialButtonState#80335)
_MaterialInterior(duration: 200ms, shape: CircleBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none)), elevation: 12.0, c
断点方行,来到[红色Consumer内部]断点,此时控制台已经显示:
I/flutter (13300): ---------GreenBox----Consumer-----build---------
I/flutter (13300): ---------YellowBox----Consumer-----build---------
再次方行此时控制台已经显示:
I/flutter (13300): ---------RedBox----Consumer-----build---------
I/flutter (13300): ---------BlueBox----Consumer-----build---------
此时界面已更新。但按钮还没缓过神
会走到buildScope,脏表数为1,
_MaterialInterior(duration: 200ms,
再放行,按钮更新,一次界面的点击刷新完成。
复制代码
如今看来惟一的关注点是ListenableProvider这个东东,何许人也?
在这幅图中已经浮现大佬的身姿了,它老爹是MutiProvider。
在buildScope中,咱们的故事便发生在ListenableProvider的rebuild方法里
进入后咱们到达
Element#rebuild()=> ComponentElement#performRebuild()
看到InheritedProvider,我也就会心一笑了。就快打完收工了。
rebuild一波后,脏表加了5个,每错,都是Consumer的节点。只要四个块,为何有5个?
百思不得其解,最后一句TMD,按钮上也加了Consumer,被本身蠢死。
众所周知,Flutter只会绘制重建脏表里的元素。因此会直接构建Consumer而非总体。
没有对比就没有伤害,最后看一下不用Consumer时重构页面的脏表状况。在rebuild一波后脏表加入的是整个Widget的元素。
就这样,因此层次较深时,推荐使用Consumer来将更新的粒度变小。
本文到此接近尾声了,若是想快速尝鲜Flutter,《Flutter七日》会是你的必备佳品;若是想细细探究它,那就跟随个人脚步,完成一次Flutter之旅。
另外本人有一个Flutter微信交流群,欢迎小伙伴加入,共同探讨Flutter的问题,本人微信号:zdl1994328
,期待与你的交流与切磋。另外欢迎关注公众号编程之王