万字长文!一文搞懂 InheritedWidget 局部刷新机制
前言
上一篇我们从源码角度分析了 setState
的过程,从而了解到为什么 setState
方法被调用的时候会重新构建整个 Widget
树。但是,Widget
树的重新构建并不意味着渲染元素树也需要重新构建,事实上渲染树只是做了更新,而不一定是移除后在渲染。
但是,我们的 ModelBinding
类也是使用了 setState
进行状态更新的,为什么它的子组件没有重新构建,而只是更新了依赖于状态的子组件的 build
方法呢?除了使用了内部的 InheritedWidget
包裹了子组件外,其他和普通的 StatefulWidget
没什么区别。如前面两篇分析 从InheritedWidget
了解状态管理一样,差别就是在这个 InheritedWidget
上。本着技术人刨根问底的精神,本篇就来看一下 InheritedWidget
在调用 setState
的时候究竟有什么不同。
知其然,知其所以然。在阅读本篇文章前,如果对 Flutter 的状态管理不是特别清楚的,建议阅读本人前几篇文章了解一下背景。
InheritedWidget 与 StatefulWidget 的区别
首先,InheritedWidget
和 StatefulWidget
的继承链不同,对比如下。
InheritedWidget
继承自 ProxyWidget
,之后才是 Widget
,而 StatefulWidget
直接继承 Widget
。其二是创建的渲染元素类不同,InheritedWidget
的 createElement
返回的是InheritedElement
,而 StatefulWidget
的 createElement
返回的是StatefulElement
。
我们在上一篇已经知道,实际的渲染控制是有 Element
类来完成的,实际上Widget
的createElement
方法就是将 Widget
对象传给 Element
对象,由 Element
对象根据 Widget
的组件配置来决定如何渲染。
InhretiedWidget
的定义很简单,如下所示:
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
。
由此我们就知道了为什么 InheritedWidget
在状态更新的时候为什么没有重新构建其子组件树了,这是因为在ProxyElement
中直接就返回了已经构建的子组件树,而不是重建。你是不是以为真相大白了?说好的刨根问底呢?难道我们不应该问问如果子组件树发生了改变,ProxyElement
是如何感知的?比如插入了一个新的元素,或者某个元素的渲染参数变了(颜色,字体,内容等),渲染层是怎么知道的?继续继续!
InheritedElement 如何感知组件树的变化
先看一下 InheritedElement
的类结构。
从类结构上看也不复杂,这是因为大部分渲染的管理已经在父类的 ComponentElement
和 Element
中完成了。build
方法我们已经讲过了,重点来看一下在 InheritedWidget
的父组件调用 setState
后的过程。我们在子组件需要获取状态管理的时候,使用的方法是:
这个方法实际调用的是:
这里的dependOnInheritedWidgetOfExactType
方法在 BuildContext
定义,但实际上是Element
实现。这里会访问一个HashMap
对象_inheritedWidgets
,从数组中找到对应类型的InheritedElement
。
这个数组实际上是在 mount
方法中调用_updateInheritance
中完成初始化的。而在InheritedElement
中重载了 Element
的这个方法。也就是在创建 InheritedWidget
的时候,在 mount
中就将 InheritedElement
与对应的组件运行时类型进行了关联。
首先这个方法会将父级的全部 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)
方法:
而在 ProxyElement
中,重写了 update
方法。
这里的 newWidget
是 setState
的时候构建的新的组件配置,因此和 oldWidget
并不相同。对于 InheritedWidget
,它会先调用updated(oldWidget)
,这个方法实际上就是通知依赖 InheirtedWidget
的组件更新:
实际上最终调用了依赖 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 的组件渲染的核心就在于如何选择状态管理来实现组件的渲染,这个对性能影响很大。
版权声明: 本文为 InfoQ 作者【岛上码农】的原创文章。
原文链接:【http://xie.infoq.cn/article/3e46426c509ac4ffe43267bd5】。文章转载请联系作者。
评论