theme: channing-cyan
highlight: darcula
这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战
前言上两篇我们介绍了使用 InheritedWidget 深入状态管理,并且解耦了组件和状态管理,从而使得代码更易于维护,而且还能实现局部刷新的效果。上两篇文章传送门:
-
Flutter入门与实战(四十一): 从InheritedWidget深入了解状态管理机制(上)
-
Flutter入门与实战(四十二): 从InheritedWidget深入了解状态管理机制(下)
到底能不能实现局部刷新呢吗,我们本篇通过一个示例来验证一下。
使用 ModelBinding 实现状态共享为了使用 ModelBinding 实现状态共享,我们需要将雷思和小芙作为ModelBinding的子组件。同时,我们另外写了一个不依赖于状态的组件(StatelessNoDepend),也放直在 ModelBinding 的子组件中,以便验证局部刷新是否有效。
class StatefulStatelessDemoPage extends StatelessWidget { StatefulStatelessDemoPage({Key key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('3年后'), ), body: ModelBindingV2( child: Column( children: [ Xiaofu3(), Leisi(), StatelessNoDepend(), ], ), create: () => FaceEmotion(emotion: '惊讶'), ), ); } }
同时,在雷思(Leisi) 组件中,我们通过一个文本来显示状态中的数据,这里是一个情绪描述字符串。这个就是从状态管理中共享得到,因为依赖于状态,因此在状态改变的时候会重新build。
@override Widget build(BuildContext context) { print('ModelBinding=====build:雷思'); return Center( child: Text( 'ModelBinding=====雷思感受到了小芙的${ModelBindingV2.of(context).emotion}'), ); }
同样的,在小芙(Xiaofu3)中,我们也会读取状态的情绪字符串,以及使用了一个按钮来改变情绪,从而使得组件刷新。
@override Widget build(BuildContext context) { print('ModelBinding=====build:小芙'); return Center( child: Column(children: [ Text( 'ModelBinding=====小芙的表情:${ModelBindingV2.of(context).emotion}'), TextButton( onPressed: () { ModelBindingV2.update ( context, FaceEmotion(emotion: '高兴')); }, child: Text('小芙表情变了')), ]), ); }
为了看是否真的被重建,我们在三个组件(Leisi,Xiaofu3和StatelessNoDepend)中的build 方法里打印了一个信息,同时我们在构造函数也打印了构造信息。运行后,我们点击按钮,整个打印的信息如下:
flutter: ModelBinding=====constructor: 小芙 flutter: ModelBinding=====constructor: 雷思 flutter: constructor: 不依赖状态的组件 flutter: ModelBinding=====build:小芙 flutter: ModelBinding=====build:雷思 flutter: build:不依赖于状态的组件 ------------------------------- flutter: ModelBinding=====build:小芙 flutter: ModelBinding=====build:雷思
分隔线以下是点击按钮后的打印信息,可以看到,第一次加载的时候,三个组件都 build 方法都被调用了。而点击按钮后,只有 Leisi 和 Xiaofu 的build 方法被调用,这说明了确实实现了局部刷新的效果 —— 不依赖于状态的组件不会被重建。
使用 setState 共享状态使用 setState的话会要复杂一点,我们需要通过父组件将数据传递给子组件,然后在状态发生改变的时候,需要调用 setState 方法更新子组件,这个时候还需要父组件的更新状态方法传递到更改状态的子组件里。显然,耦合度是很高的。
class SetStateDemo extends StatefulWidget { SetStateDemo({Key key}) : super(key: key); _SetStateDemoState createState() => _SetStateDemoState(); } class _SetStateDemoState extends State{ FaceEmotion faceEmotion = FaceEmotion(); void updateEmotion(FaceEmotion newEmotion) { if (faceEmotion != newEmotion) { setState(() { faceEmotion = newEmotion; }); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('setState 方式'), ), body: Column( children: [ StatelessXiaofu(face: faceEmotion, updateEmotion: updateEmotion), StatelessLeisi(face: faceEmotion), StatelessNoDepend(), ], ), ); } }
剩下的代码很简单,就不贴了,现在看进入页面和点击按钮更改状态后的整个过程。
flutter: setState=====constructor:小芙 flutter: setState=====constructor: 雷思 flutter: constructor: 不依赖状态的组件 flutter: setState=====build:小芙 flutter: setState=====build:雷思 flutter: build:不依赖于状态的组件 ------------------------------ flutter: setState=====constructor:小芙 flutter: setState=====constructor: 雷思 flutter: constructor: 不依赖状态的组件 flutter: setState=====build:小芙 flutter: setState=====build:雷思 flutter: build:不依赖于状态的组件
可以看到,第一次进入页面的过程和ModelBinding是一样的。但是,在点击按钮调用setState方法的时候就完全不一样了,使用ModelBinding方法只是调用了 依赖于状态的build方法,而setState之后全部子组件被重新构造了一遍,也就是移除后再插入了新的组件——这就性能消耗和 ModelBinding相比,肯定高很多。那么,setState 的过程到底发生了什么?我们下一篇通过源码来分析一下。
总结本篇对比了使用 InheritedWidget 实现状态共享和使用 setState 方式实现状态共享的区别,很明显,使用 InheritedWidget的方式性能更高,可以实现局部刷新,而且不会出现 setState 那种重构整个组件树的情况。这个特点十分重要,意味着我们要尽可能地避免在高层级的组件上直接使用 setState刷新界面,而是要依赖于状态管理实现局部刷新。当然,如果这个组件本身是组件树的叶子节点,那么使用 setState 不会有什么性能损失,这个时候倒是没必要非得使用状态管理工具。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)