这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战数组
上一篇咱们从源码角度分析了 setState 的过程,从而了解到为何 setState 方法被调用的时候会从新构建整个 Widget 树。可是,Widget 树的从新构建并不意味着渲染元素树也须要从新构建,事实上渲染树只是作了更新,而不必定是移除后在渲染。微信
可是,咱们的 ModelBinding类也是使用了 setState 进行状态更新的,为何它的子组件没有从新构建,而只是更新了依赖于状态的子组件的 build 方法呢?除了使用了内部的 InheritedWidget包裹了子组件外,其余和普通的 StatefulWidget 没什么区别。如前面两篇分析 从InheritedWidget了解状态管理同样,差异就是在这个 InheritedWidget上。本着技术人刨根问底的精神,本篇就来看一下 InheritedWidget 在调用 setState的时候究竟有什么不一样。markdown
知其然,知其因此然。在阅读本篇文章前,若是对 Flutter 的状态管理不是特别清楚的,建议阅读前几篇文章了解一下背景:app
首先,InheritedWidget
和 StatefulWidget
的继承链不一样,对好比下。
InheritedWidget
继承自 ProxyWidget
,以后才是 Widget
,而 StatefulWidget
直接继承 Widget
。 其二是建立的渲染元素类不一样,InheritedWidget
的 createElement
返回的是InheritedElement
,而 StatefulWidget
的 createElement
返回的是StatefulElement
。less
咱们在上一篇已经知道,实际的渲染控制是有 Element
类来完成的,实际上Widget
的createElement
方法就是将 Widget
对象传给 Element
对象,由 Element
对象根据 Widget
的组件配置来决定如何渲染。ide
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
的区别了。post
上一篇咱们已经分析过 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 的类结构。
classDiagram Element <-- ComponentElement ComponentElement <-- ProxyElement ProxyElement <-- InheritedElement class Element { -dependOnInheritedWidgetOfExactType() -dependOnInheritedElement() } class InheritedElement { -Map<Element, Object?> _dependents -void _updateInheritance() -getDependencies(Element dependent) setDependencies(Element dependent, Object? value) updateDependencies(Element dependent, Object? aspect) notifyDependent(covariant InheritedWidget oldWidget, Element dependent) updated(InheritedWidget oldWidget) notifyClients(InheritedWidget oldWidget) } class ProxyElement { -build() -update(ProxyWidget newWidget) -updated(covariant ProxyWidget oldWidget) -notifyClients(covariant ProxyWidget oldWidget) }
从类结构上看也不复杂,这是由于大部分渲染的管理已经在父类的 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
配置树一旦建立就不会改变,而子组件树若是要 改变的话只有两种状况: 一、子组件是 StatefulWidget
,经过setState
改变,那这不属于 InheritedWidget 的范畴了,而是经过 StatefulWidget 的更新方式完成——固然,这种作法不推荐。 二、子组件的组件树改变依赖于状态吗,那这个时候天然会在状态改变的时候更新。
由此,咱们终于弄明白了InheritedWidget的组件树的感知和通知子组件刷新过程。
从 InheritedWidget 实现组件渲染的过程来看,整个过程分为下面几个步骤:
状态管理的原理性文章讲了好几篇了,经过这些文章但愿可以达到知其然,知其因此然的目的。实际上,Flutter 的组件渲染的核心就在于如何选择状态管理来实现组件的渲染,这个对性能影响很大。接下来咱们将以状态管理插件的应用方式,讲述在实际例子中的应用。
我是岛上码农,微信公众号同名,这是Flutter 入门与实战的专栏文章。
👍🏻:以为有收获请点个赞鼓励一下!
🌟:收藏文章,方便回看哦!
💬:评论交流,互相进步!