Flutter InheritedWidget实现原理

前言


在Flutter开发中,对于一些简单的数据传递,我们可以使用Widget constructor直接传递进去,但是当某个后代Widget依赖上层的祖先Widget或者多个Widget同时依赖祖先Widget的情形时,直接传递的方式就会暴露出很多的问题:需要连续传递很难维护以及依赖的祖先Widget数据改变时,所有子Widget都需要rebuild。

Flutter提供了另外一个机制来解决这个问题,InheritedWidget,当InheritedWidget数据发生变化时,只通知依赖其数据变化的Widgets,接下来将按步骤分析实现细节。( Provider也是基于InheritedWidget进行的封装)

实现细节


我们通过一个小demo来展示使用方法,假设数据Color需要被后代Widget访问,且当Color变化,后代Widget能监听到变化。步骤比较简单,先子类InheritedWidget,如下代码示例,FrogColor定义了一个名为of的静态方法,该方法由后代Widget进行调用,调用时将后代Widget的Element作为context传入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class FrogColor extends InheritedWidget {
const FrogColor({
Key key,
@required this.color,
@required Widget child,
}) : assert(color != null),
assert(child != null),
super(key: key, child: child);

final Color color;

static FrogColor of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<FrogColor>();
}

@override
bool updateShouldNotify(FrogColor old) => color != old.color;
}

我们看下of静态方法的实现,实际调用的是后代Widget对应Element的 dependOnInheritedWidgetOfExactType,该方法首先会去_inheritedWidgets里找是否有需要的InheritedWidget,如果找到,返回找到的InheritedWidget,并将自己作为依赖注册到InheritedWidget里,这样InheritedWidget就能知道哪些Widget依赖它,如果InheritedWidget发生了变化,会通知依赖方。_inheritedWidgets会在InheritedWidget mount的时候从父Element获取当前所有的InheritedWidgets,并且把自己也加到里边,也就是说,所有的Element都有一个Map来收集当前祖先所有的InheritedWidget。

1
2
3
4
5
6
7
8
9
10
11
@override
T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
if (ancestor != null) {
assert(ancestor is InheritedElement);
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}

重新回到FrogColor类,另一个方法updateShouldNotify,当FrogColor widget rebuild时,FrogColor的Element会通过updated方法来判断是否需要通知所有依赖它的后代Widgets,super.updated(oldWidget)用来通知所有的dependencies 依赖方。

1
2
3
4
5
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}

当需要通知依赖方的时候,调用依赖Widget对应Element的didChangeDependencies方法,Element将自己标记为dirty,并加到BuildOwner的dirty列表中,当下一帧绘制时,会重新build Widget。

1
2
3
4
5
6
@mustCallSuper
void didChangeDependencies() {
assert(_active); // otherwise markNeedsBuild is a no-op
assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
markNeedsBuild();
}

总结


综上,我们可以看到,InheritedWidget不仅能解决多层传递带来的业务复杂度,也能非常高效的进行rebuild操作。