在dart中说到状态管理,想必大家都知道InheritedWidget,这里说一下我对InheritedWidget的理解
InheritedWidget本质有两大功能,
- InheritedWidget数据向下传递(下层节点可以获取到InheritedWidget中的数据)
- InheritedWidget的状态绑定(就是InheritedWidget被修改,会导致引用的地方数据刷新)
1.InheritedWidget数据向下传递
InheritedWidget 本质是一个Widget,Widget本身要创建一个Element,而Element中包含一个属性
PersistentHashMap<Type, InheritedElement>? _inheritedWidgets;
这个属性是一个Map其中存储着本身及上层所有的InheritedElement,每次创建新的Element,都会从上一层Element中获取到_inheritedWidgets,并赋值给自己的_inheritedWidgets
void _updateInheritance() {
assert(_lifecycleState == _ElementLifecycle.active);
_inheritedWidgets = _parent?._inheritedWidgets;
}
上面说了InheritedWidget是个Widget,所以会想到它也应该会创建一个Element,没错这里创建的就是InheritedElement
abstract class InheritedWidget extends ProxyWidget {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const InheritedWidget({ super.key, required super.child });
@override
InheritedElement createElement() => InheritedElement(this);
...
进去后发现InheritedElement重写了_updateInheritance,在之前的基础上将自己加入到_inheritedWidgets中
@override
void _updateInheritance() {
assert(_lifecycleState == _ElementLifecycle.active);
final PersistentHashMap<Type, InheritedElement> incomingWidgets =
_parent?._inheritedWidgets ?? const PersistentHashMap<Type, InheritedElement>.empty();
_inheritedWidgets = incomingWidgets.put(widget.runtimeType, this);
}
到这里我们就可以清晰的看到如果是普通的Element只需要获取到上一级Element的_inheritedWidgets,而InheritedElement会将自己也加入进去,如果此时有了下一级Element,那么他的_inheritedWidgets就包含了上一级的InheritedElement,这样设计的目的应该也是为了方便查询(不用递归了)
之后dependOnInheritedWidgetOfExactType这个方法会通过_inheritedWidgets找到对应的InheritedElement,以及InheritedWidget.
顺便发现如果上层有同一个类型的InheritedWidget,这里会将InheritedWidget的runtimeType
作为key,会顶掉之前的InheritedWidget,所以在下层是访问不到之前的InheritedWidget
@override
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
if (ancestor != null) {
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies!.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget as InheritedWidget;
}
到这里关于InheritedWidget获取逻辑应该是很清楚了.
2.InheritedWidget的状态绑定
举个例子Theme.of(context)这个在我们的项目中经常使用,我们通过这几代码来获取ThemeData,当ThemeData发生改变后,Theme.of(context)的地方都会刷新
Theme.of(context)
这个里边其实调用的就是dependOnInheritedWidgetOfExactType,然后调用dependOnInheritedElement,而在dependOnInheritedElement中我们会发现,这样一句代码
ancestor.updateDependencies(this, aspect);
这里其实就是将context(其实就是element)添加到了InheritedElement的_dependents中
final Map<Element, Object?> _dependents = HashMap<Element, Object?>();
当InheritedWidget修改了之后会调用InheritedElement 的 update函数
@override
void update(ProxyWidget newWidget) {
final ProxyWidget oldWidget = widget;
assert(widget != null);
assert(widget != newWidget);
super.update(newWidget);
assert(widget == newWidget);
updated(oldWidget);
_dirty = true;
rebuild();
}
之后在updated会调用notifyClients
// updateShouldNotify可以控制是否调用super的updated
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
@override
void notifyClients(InheritedWidget oldWidget) {
assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
for (final Element dependent in _dependents.keys) {
assert(() {
// check that it really is our descendant
Element? ancestor = dependent._parent;
while (ancestor != this && ancestor != null) {
ancestor = ancestor._parent;
}
return ancestor == this;
}());
// check that it really depends on us
assert(dependent._dependencies!.contains(this));
notifyDependent(oldWidget, dependent);
}
}
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
在didChangeDependencies中进行了对应element的重新build,setState本质也是调用了markNeedsBuild
@mustCallSuper
void didChangeDependencies() {
assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op
assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
markNeedsBuild();
}
而State中也有didChangeDependencies方法(经常会问到的生命周期函数),会在Element 的didChangeDependencies调用后,重新build的时候调用的
@override
void performRebuild() {
if (_didChangeDependencies) {
state.didChangeDependencies();
_didChangeDependencies = false;
}
super.performRebuild();
}
_didChangeDependencies这个参数也是在StateFulElement中重写的didChangeDependencies中设置成了true
@override
void didChangeDependencies() {
super.didChangeDependencies();
_didChangeDependencies = true;
}
到这里InheritedWidget的两大功能就说完了, InheritedWidget的向下传递以及InheritedWidget的状态绑定,统称为状态管理
补充:
之前有个问题我困惑了很久,就是InheritedWidget及其以下的widget,本身就会因为顶层的setState而刷新,为什么需要有_dependents的存在,去标记_didChangeDependencies呢?或许今天遇到的一个问题可以解释这样做的目的.
我的StatefulWidget使用const修饰的话,顶层的setState并不会导致const修饰的widget 重新 build,所以如果没有_dependents,对于const 修饰的widget我们就没办法去刷新,这个应该是_dependents存在的意义.
那为什么const修饰的widget不会重新执行build方法呢?
这里通过看源码发现build方法执行通道有两个,我觉得更好理解的是一个叫做刷新,一个叫做更新,刷新源头来自于本身,而更新源头来自于父组件
-
1.通过设置element _dirty来标记(我就叫做刷新),下一帧绘制的时候强制刷新
-
2.是通过element树遍历来执行刷新(我就叫做更新)
(遍历的这些element 的 _dirty 并不一定都是true,但是会重新执行build方法,这一点理解很重要,很多同学理解的build执行的前提是element被设置成 _dirty 是不对的)
方式2,因为在Element的updateChild
方法中有这样一句代码
if (hasSameSuperclass && child.widget == newWidget) {
if (child.slot != newSlot) {
updateSlotForChild(child, newSlot);
}
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
...
child.update(newWidget);
...
newChild = child;
}
因为const修饰的widget是同一个对象,导致了 child.widget == newWidget成立,所以update(newWidget)不会执行,导致通过方法2,没有办法来实现build重新执行
而对于InheritedElement,本质就是一个Element,它也会在方式2中被遍历到,并且执行自己的update方法,但是在执行update方法时会有一个updated方法被执行,这个方法中会将_dependents中的所有element._dirty设置成true(其实markNeedsBuild,就是干这个事情的),这样就可以强制去刷新了.