Flutter状态管理(零)——InheritedWidget
Flutter的状态管理是开发Flutter应用必须要面对的一个问题. 由于Flutter还是一个很新的技术, 虽然目前有很多的状态管理框架, 但是因为发展的时间都不长, 所以它们的优点和相对的不足都比较突出. 这个新的系列就是来向大家介绍目前Flutter主流的一些状态管理的解决方案, 希望大家能够从中有所收获.
这篇文章是状态管理系列的第零篇. 为什么是第零篇呢? 因为文章要介绍的InheritedWidget虽然是状态管理的一种解决方案, 但是它仅仅是Flutter本身提供的一个简单的组件, 并不是一个完整的框架(或者说功能不强, 实现比较底层). 但是, 作为Flutter自己提供的一种解决方案, 它又是我们要学习其它框架时必须要了解的内容(因为它涉及到Flutter内部状态传递的实现).
废话就先说到这, 我们进入正题.
InheritedWidget的使用
1 | void main() => runApp(new DemoApp()); |
上面是一个简单的使用InheritedWidget的Demo. 代码也很好懂. 我们用InheritedWidget作为一个数据仓库, 里面存储了要被其它组件使用的数据——String类型的data. 之后我们用一个StatelessWidget作为数据的消费者, 消费者首先通过context获取InheritedWidget的实例, 然后通过这个实例取出存储的数据并使用.
下面我们就根据这个Demo来学习InheritedWidget的实现. (下面的内容需要对BuildContext、Element有一定了解)
InheritedWidget的实现
首先我们先看一下InheritedWidget的源码.
1 | abstract class InheritedWidget extends ProxyWidget { |
作为一个和StatelessWidget、StatefulWidget同级的抽象类, InheritedWidget的源码也只有简单的三个函数. 除去构造函数, 实际上值得我们进一步学习的只有createElement和updateShouldNotify这两个函数.
InheritedElement
首先我们看一下InheritedElement. 和别的Element相比, InheritedElement的主要区别在于_updateInheritance方法的实现.
1 | //普通Element的实现 |
上面是普通的Element和InheritedElement关于_updateInheritance方法不同实现的对比. 首先我们先解释一下函数中出现的两个成员变量: _inheritedWidgets和_parent.
_parent很简单, 就是Element在Element树上的父节点.
_inheritedWidgets是一个HashMap, 它的类型为HashMap<Type, InheritedElement<. 可以看到, 这个属性就是专门用来管理InheritedElement的.
普通Element的_updateInheritance函数实现很简单: 如果父节点的_inheritedWidgets不为空, 那么就继承它的_inheritedWidgets, 否则就令自己的_inheritedWidgets为空. InheritedElement的_updateInheritance函数实现其实也不难, 就是在继承父节点的_inheritedWidgets的情况下, 再把自己加入这个Map中. 这样, 所有的InheritedElement就会通过_updateInheritance这个方法从Element的树根层层下沉到树的各个节点, 这样所有的Element就可以通过自己的_inheritedWidgets来获取到指定的InheritedWidget的数据了.
这里再说一个小问题: _updateInheritance函数是在什么时候执行的呢? 它是在Element的mount和active阶段被调用的. 关于Element的生命周期, 可以查看谷歌官方给出的介绍, 我觉得已经很详细了: https://api.flutter.dev/flutter/widgets/Element-class.html
inheritFromWidgetOfExactType
前面我们说到, Element可以通过自己的_inheritedWidgets来获取到指定的InheritedWidget, 那么我们应该怎么获取呢? 从Demo中我们看到, 调用的是context提供的inheritFromWidgetOfExactType方法.
1 | InheritedWidget inheritFromWidgetOfExactType(Type targetType) { |
面是inheritFromWidgetOfExactType函数的代码. 因为本身做的就是从HashMap取出Element的简单操作, 所以逻辑并没有很复杂. 但是我们发现, 函数除了执行从HashMap中取出Element的操作, 还维护了属于该Element的一个名为_dependents的HashSet. 这是做什么用的呢?
notifyClients
在文章开头的demo中, InheritedWidget的data是不变的. 那么如果InheritedWidget的data是可变的呢? 显然, 在InheritedWidget发生变化后, InheritedElement需要通知所有使用它的Element自己的Widget发生了变化, 这就用到了它的notifyClients函数.
1 | void notifyClients(InheritedWidget oldWidget) { |
上面是notifyClients函数的源码. 这里我们就看到了_dependents的作用——用来通知其它Element自己发生了变化. 至于怎么判定自己是否发生了变化, 这就用到了我们自己实现的updateShouldNotify函数了.
总结
InheritedWidget的实现比较简单, 但是流程又比较碎, 所以这里再整理一下文章的内容, 用几句话对InheritedWidget做一个总结:
InheritedWidget实际上不是一个UI控件, 它更像是一个数据仓库, 存储着需要共享给别的控件使用的数据.
一个程序中的所有InheritedWidget都由一个HashMap维护. 这个HashMap从Element树的根节点开始向下传递, 使得每个Element都可以通过inheritFromWidgetOfExactType函数获取到在其上层的所有InheritedWidget.
InheritedWidget采用了观察者模式. 所有需要使用InheritedElement的Element在调用inheritFromWidgetOfExactType时也会将自己注册在使用的InheritedElenemt的观察者中.
InheritedElement通过updateShouldNotify来判断是否通知所有的观察者自己发生了变化. 观察的Element在收到通知后调用自己的didChangeDependencies函数来针对变化做相应的逻辑处理.