Flutter状态管理(零)——InheritedWidget

Flutter的状态管理是开发Flutter应用必须要面对的一个问题. 由于Flutter还是一个很新的技术, 虽然目前有很多的状态管理框架, 但是因为发展的时间都不长, 所以它们的优点和相对的不足都比较突出. 这个新的系列就是来向大家介绍目前Flutter主流的一些状态管理的解决方案, 希望大家能够从中有所收获.

这篇文章是状态管理系列的第零篇. 为什么是第零篇呢? 因为文章要介绍的InheritedWidget虽然是状态管理的一种解决方案, 但是它仅仅是Flutter本身提供的一个简单的组件, 并不是一个完整的框架(或者说功能不强, 实现比较底层). 但是, 作为Flutter自己提供的一种解决方案, 它又是我们要学习其它框架时必须要了解的内容(因为它涉及到Flutter内部状态传递的实现).

废话就先说到这, 我们进入正题.

InheritedWidget的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void main() => runApp(new DemoApp());

class DataRepo extends InheritedWidget {
DataRepo({Key key, this.data, Widget child})
: super(key: key, child: child);

final String data;

@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return oldWidget.data != welcomeInfo;
}
}

class DataConsumer extends StatelessWidget {
@override
build(BuildContext context) {
final DataRepo repo = context.inheritFromWidgetOfExactType(DataRepo);
return Text(widget.data);
}
}

class DemoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter InheritWidget',
home: DataRepo(
data: 'hello flutter',
child: Center(
child: DataConsumer(),
),
),
);
}
}

上面是一个简单的使用InheritedWidget的Demo. 代码也很好懂. 我们用InheritedWidget作为一个数据仓库, 里面存储了要被其它组件使用的数据——String类型的data. 之后我们用一个StatelessWidget作为数据的消费者, 消费者首先通过context获取InheritedWidget的实例, 然后通过这个实例取出存储的数据并使用.

下面我们就根据这个Demo来学习InheritedWidget的实现. (下面的内容需要对BuildContext、Element有一定了解)

InheritedWidget的实现

首先我们先看一下InheritedWidget的源码.

1
2
3
4
5
6
7
8
9
10
abstract class InheritedWidget extends ProxyWidget {
const InheritedWidget({ Key key, Widget child })
: super(key: key, child: child);

@override
InheritedElement createElement() => new InheritedElement(this);

@protected
bool updateShouldNotify(covariant InheritedWidget oldWidget);
}

作为一个和StatelessWidget、StatefulWidget同级的抽象类, InheritedWidget的源码也只有简单的三个函数. 除去构造函数, 实际上值得我们进一步学习的只有createElement和updateShouldNotify这两个函数.

InheritedElement

首先我们看一下InheritedElement. 和别的Element相比, InheritedElement的主要区别在于_updateInheritance方法的实现.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//普通Element的实现
void _updateInheritance() {
assert(_active);
_inheritedWidgets = _parent?._inheritedWidgets;
}

//InheritedElement的实现
void _updateInheritance() {
assert(_active);
final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = new HashMap<Type, InheritedElement>.from(incomingWidgets);
else
_inheritedWidgets = new HashMap<Type, InheritedElement>();
_inheritedWidgets[widget.runtimeType] = this;
}

上面是普通的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
2
3
4
5
6
7
8
9
10
11
12
13
InheritedWidget inheritFromWidgetOfExactType(Type targetType) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
if (ancestor != null) {
assert(ancestor is InheritedElement);
_dependencies ??= new HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor._dependents.add(this);
return ancestor.widget;
}
_hadUnsatisfiedDependencies = true;
return null;
}

面是inheritFromWidgetOfExactType函数的代码. 因为本身做的就是从HashMap取出Element的简单操作, 所以逻辑并没有很复杂. 但是我们发现, 函数除了执行从HashMap中取出Element的操作, 还维护了属于该Element的一个名为_dependents的HashSet. 这是做什么用的呢?

notifyClients

在文章开头的demo中, InheritedWidget的data是不变的. 那么如果InheritedWidget的data是可变的呢? 显然, 在InheritedWidget发生变化后, InheritedElement需要通知所有使用它的Element自己的Widget发生了变化, 这就用到了它的notifyClients函数.

1
2
3
4
5
6
7
8
void notifyClients(InheritedWidget oldWidget) {
if (!widget.updateShouldNotify(oldWidget))
return;

for (Element dependent in _dependents) {
dependent.didChangeDependencies();
}
}

上面是notifyClients函数的源码. 这里我们就看到了_dependents的作用——用来通知其它Element自己发生了变化. 至于怎么判定自己是否发生了变化, 这就用到了我们自己实现的updateShouldNotify函数了.

总结

InheritedWidget的实现比较简单, 但是流程又比较碎, 所以这里再整理一下文章的内容, 用几句话对InheritedWidget做一个总结:

  1. InheritedWidget实际上不是一个UI控件, 它更像是一个数据仓库, 存储着需要共享给别的控件使用的数据.

  2. 一个程序中的所有InheritedWidget都由一个HashMap维护. 这个HashMap从Element树的根节点开始向下传递, 使得每个Element都可以通过inheritFromWidgetOfExactType函数获取到在其上层的所有InheritedWidget.

  3. InheritedWidget采用了观察者模式. 所有需要使用InheritedElement的Element在调用inheritFromWidgetOfExactType时也会将自己注册在使用的InheritedElenemt的观察者中.

  4. InheritedElement通过updateShouldNotify来判断是否通知所有的观察者自己发生了变化. 观察的Element在收到通知后调用自己的didChangeDependencies函数来针对变化做相应的逻辑处理.