前言
上两篇我们介绍了使用 InheritedWidget
深入状态管理,并且解耦了组件和状态管理,从而使得代码更易于维护,而且还能实现局部刷新的效果。
到底能不能实现局部刷新呢吗,我们本篇通过一个示例来验证一下。
假设
回到我们前一个故事 —— 从相亲来看 Flutter 的 StatefulWidget 和 StatelessWidget,假设我们的雷思不是那么木,他能够读懂小芙的心思(共享状态),那么也许结局可能就不是之前那样。所谓心有灵犀一点通,我们分别通过ModelBinding
和初级的setState
方式来实现这样的效果。
使用 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<FaceEmotion>(context).emotion}'),
);
}
复制代码
同样的,在小芙(Xiaofu3)中,我们也会读取状态的情绪字符串,以及使用了一个按钮来改变情绪,从而使得组件刷新。
@override
Widget build(BuildContext context) {
print('ModelBinding=====build:小芙');
return Center(
child: Column(children: [
Text(
'ModelBinding=====小芙的表情:${ModelBindingV2.of<FaceEmotion>(context).emotion}'),
TextButton(
onPressed: () {
ModelBindingV2.update<FaceEmotion>(
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<SetStateDemo> {
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 不会有什么性能损失,这个时候倒是没必要非得使用状态管理工具。
评论