一文搞懂InheritedWidget局部刷新机制
Posted 一叶飘舟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文搞懂InheritedWidget局部刷新机制相关的知识,希望对你有一定的参考价值。
InheritedWidget与 StatefulWidget 的区别
首先,InheritedWidget
和 StatefulWidget
的继承链不同,对比如下。
InheritedWidget
继承自 ProxyWidget
,之后才是 Widget
,而 StatefulWidget
直接继承 Widget
。 其二是创建的渲染元素类不同,InheritedWidget
的 createElement
返回的是InheritedElement
,而 StatefulWidget
的 createElement
返回的是StatefulElement
。
我们在上一篇已经知道,实际的渲染控制是有 Element
类来完成的,实际上Widget
的createElement
方法就是将 Widget
对象传给 Element
对象,由 Element
对象根据 Widget
的组件配置来决定如何渲染。
InhretiedWidget
的定义很简单,如下所示:
abstract class InheritedWidget extends ProxyWidget
const InheritedWidget(Key? key, required Widget child)
: super(key: key, child: child);
@override
InheritedElement createElement() => InheritedElement(this);
@protected
bool updateShouldNotify(covariant InheritedWidget oldWidget);
updateShouldNotify
方法用于 InheritedWidget
的子类实现,已决定是否通知其子组件(widget
)。例如,如果数据没有发生改变(典型的如下拉刷新没有新的数据),那么就可以返回 false
,从而无需更新子组件,减少性能消耗。之前我们的 ModelBinding
例子中是直接返回了 true
,也就是每次发生变化都会通知子组件。接下来就看 InheritedElement
和 StatefulElement
的区别了。
InheritedElement 与 StatefulElement 的区别
上一篇我们已经分析过 StatefulElement
了,他在 setState
后会调用重建方法 performRebuild
。performRebuild
方法在父类Component
中实现的。核心是当 Widget
树发生改变后,根据新的 Widget
树调用 updateChild
方法来更新子元素。
而上一篇的 ModelBinding
调用 setState
的时候,因为它自身是一个 StatefulWidget
,毫无疑问它也会调用到 updateChild
来更新子元素。从执行结果来看,由于 ModelBinding
的例子中没有出现重新构建 Widget
树的情况,因此应该是在 updateChild
前的处理不同。 在 updateChild
之前会调用组件的 build
方法来获取新的 Widget
树。是这里不同吗?继续往下看。
与 InheritedWidget
对应,InheritedElement
上面还多了一层继承,那就是 ProxyElement
。而恰恰在 ProxyElement
我们找到了build
方法。与 StatefulElement
不同,这里的 build
方法没有调用对应 Widget
对象的 build
方法,而是直接返回了 widget.child
。
// ProxyElement的 build 方法
@override
Widget build() => widget.child;
// StatefulElement 的 build 方法
@override
Widget build() => state.build(this);
// StatelessElement 的 build方法
@override
Widget build() => widget.build(this);
由此我们就知道了为什么 InheritedWidget
在状态更新的时候为什么没有重新构建其子组件树了,这是因为在ProxyElement
中直接就返回了已经构建的子组件树,而不是重建。你是不是以为真相大白了?说好的刨根问底呢?难道我们不应该问问如果子组件树发生了改变,ProxyElement
是如何感知的?比如插入了一个新的元素,或者某个元素的渲染参数变了(颜色,字体,内容等),渲染层是怎么知道的?继续继续!
InheritedElement如何感知组件树的变化
先看一下 InheritedElement 的类结构。
从类结构上看也不复杂,这是因为大部分渲染的管理已经在父类的 ComponentElement
和 Element
中完成了。build
方法我们已经讲过了,重点来看一下在 InheritedWidget
的父组件调用 setState
后的过程。 我们在子组件需要获取状态管理的时候,使用的方法是:
ModelBindingV2.of<FaceEmotion>(context)
这个方法实际调用的是:
_ModelBindingScope<T> scope =
context.dependOnInheritedWidgetOfExactType(aspect: _ModelBindingScope);
这里的dependOnInheritedWidgetOfExactType
方法在 BuildContext
定义,但实际上是Element
实现。这里会访问一个HashMap
对象_inheritedWidgets
,从数组中找到对应类型的InheritedElement
。
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor,
Object? aspect)
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies!.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
@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;
这个数组实际上是在 mount
方法中调用_updateInheritance
中完成初始化的。而在InheritedElement
中重载了 Element
的这个方法。也就是在创建 InheritedWidget
的时候,在 mount
中就将 InheritedElement
与对应的组件运行时类型进行了关联。
@override
void _updateInheritance()
assert(_lifecycleState == _ElementLifecycle.active);
final Map<Type, InheritedElement>? incomingWidgets =
_parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
else
_inheritedWidgets = HashMap<Type, InheritedElement>();
_inheritedWidgets![widget.runtimeType] = this;
首先这个方法会将父级的全部 InheritedWidgets延续下来,然后在将自己(InheritedElement)存入到这个 HashMap中,以便后续能够找到该元素。
因此,当在子组件中使用dependOnInheritedWidgetOfExactType
的时候,实际上执行的是 dependOnInheritedElement
方法,传递的参数是通过类型找到的 InheritedElement
元素和指定的 InheritedWidget
类型参数 aspect
,这里就是我们的_ModeBindScope<T>
,然后会将当前的渲染元素(Element 子类)与其绑定,告知 InheritedElement
对象这个组件会依赖于它的InheritedWidget
。我们从调试的结果可以看到,在_dependents
中存在了这么一个对象。就这样,InheritedElement
就和组件对应的渲染元素建立了联系。
接下来就是看 setState
后,怎么获取新的组件树和更新组件了。我们已经知道了setState
的时候会调用 performRebuild
方法,在 performRebuild
中会调用 Element
的 updateChild
方法,现在来看InheritedElement
的updateChild
做了什么事情。实际上 updateChild
会调用 child.update(newWidget)
方法:
else if (hasSameSuperclass &&
Widget.canUpdate(child.widget, newWidget))
if (child.slot != newSlot) updateSlotForChild(child, newSlot);
child.update(newWidget);
//...
newChild = child;
// ...
return newChild;
而在 ProxyElement
中,重写了 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();
这里的 newWidget 是 setState 的时候构建的新的组件配置,因此和 oldWidget 并不相同。对于 InheritedWidget,它会先调用updated(oldWidget),这个方法实际上就是通知依赖 InheirtedWidget 的组件更新:
@protected
void updated(covariant ProxyWidget oldWidget)
notifyClients(oldWidget);
// InheritedElement类
@override
void updated(InheritedWidget oldWidget)
if (widget.updateShouldNotify(oldWidget)) super.updated(oldWidget);
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent)
dependent.didChangeDependencies();
@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);
实际上最终调用了依赖 InheritedWidget 组件渲染元素的 didChangeDependencies 方法,我们在这个方法打印出来看一下。
在元素的 didChangeDependencies
中就会调用 markNeedsBuild
将元素标记为需要更新,然后后续的过程就和 StatefulElement
的一样了。而对于没有依赖状态的元素,因为没有在_dependent
中,因此不会被更新。 而 ModelBinding
所在的组件是 StatelessWidget
,因此最初的这个 Widget
配置树一旦创建就不会改变,而子组件树如果要 改变的话只有两种情况: 1、子组件是 StatefulWidget
,通过setState
改变,那这不属于 InheritedWidget 的范畴了,而是通过 StatefulWidget 的更新方式完成——当然,这种做法不推荐。 2、子组件的组件树改变依赖于状态吗,那这个时候自然会在状态改变的时候更新。
由此,我们终于弄明白了InheritedWidget的组件树的感知和通知子组件刷新过程。
总结
从 InheritedWidget 实现组件渲染的过程来看,整个过程分为下面几个步骤:
- mount 阶段将组件树运行时类型与对应的 InheritedElement绑定,存入到 _inheritedWidgets 这个 HashMap 中;
- 在子组件添加对状态的依赖的时候,实际上将子组件对应的 Element 元素与InheritedElement(具体的 Element 对象从_inheritedWidgets中获取)进行了绑定,存入到了_dependents 这个 HashMap 中;
- 当状态更新的时候,InheritedElement 直接使用旧的组件配置通知子元素的依赖发生了改变,这是通过调用Element 的 didChangeDependencies 方法完成的。
- 在Element的didChangeDependencies将元素标记为需要更新,等待下一帧刷新。
- 而对于没有依赖状态的子组件,则不会被加入到_dependent 中,因此不会被通知刷新,进而提高性能。
状态管理的原理性文章讲了好几篇了,通过这些文章希望能够达到知其然,知其所以然的目的。实际上,Flutter 的组件渲染的核心就在于如何选择状态管理来实现组件的渲染,这个对性能影响很大。接下来我们将以状态管理插件的应用方式,讲述在实际例子中的应用。
以上是关于一文搞懂InheritedWidget局部刷新机制的主要内容,如果未能解决你的问题,请参考以下文章