目录
1、Widget、Element、RenderObject
2、Flutter中树结构的创建、更新、复用
3、DOM、Virtual DOM、diff
4、Key的分类及使用
5、参考资料
1、Widget、Element、RenderObject
Widget
- Flutter的基石,用户界面的不可变的一种描述;会频繁创建、销毁。
- 作用类似于HTML中的标签。

Element
- Widget的实例化对象,在树中详细的位置。
- 利用Widget作为配置,用于管理Widget的生命周期、UI的更新。

RenderObject
- 由Element的子类RenderObjectElement创建,负责UI的布局、绘制、事件响应。
- 开发复杂视图时,可以自定义绘制功能。

Widget、Element、RenderObject之间的关系
- Element同时持有对Widget和RenderObject的引用。
- 源码如图所示:

2、Flutter中树结构的创建、更新、复用
Flutter中的树结构

为什么创建3棵树?
- 提高性能
1
2尽可能的复用Element和RenderObject,因为Widget可能会频繁的创建销毁,
因此WidgetTree是非常不稳定的,如果每次直接根据WidgetTree重新创建RenderObjectTree会极大的消耗性能。 - 便于访问状态、树节点之间的结构信息等数据
1
2
3例如:
StatelessElement内部存储了_widget、 _renderObject;
对于StatefulElement其内部还存储了_state信息。
树的创建过程
整体流程图

1、Flutter主入口,main()函数。

2、初始化Flutter功能组件。

3.1、创建根Widget(即RenderObjectToWidgetAdapter)
- 内部创建了RenderObjectToWidgetAdapter ,并将我们传入的自定义Widget(即runApp)做为其child;
- RenderObjectToWidgetAdapter本身是一个RenderObjectWidget,是RenderObject和Element之间的桥梁。

3.2、创建根Element(即RenderObjectToWidgetElement)
- 接着执行attachToRenderTree()方法,创建根Element,并调用mount()方法,继续创建根RenderObject。

3.3、创建根RenderObject
- 在根Element中调用了mount()方法后,会调用super.mount()方法,即RenderObjectElement.mount(),创建根RenderObject,并将其挂载。

4、调用scheduleWarmUpFrame方法。
- 此时WidgetTree、ElementTree、RenderObjectTree对应的结构都已经初步建立,Flutter准备界面渲染和显示。

Flutter中树的更新
更新规则

问题:在更新过程中,如何知道Widget能够复用Element呢?
答案:Widget提供了一个核心方法canUpdate,如源码所示:

- 默认情况下:Widget的Key == null。
1 | 当没有给Widget设置Key的时候,Flutter会根据Widget的runtimeType和显示顺序是否相同来判断Widget是否有变化。(runtimeType即Widget的类型) |
- 当给Widget设置了Key时。
1 | 当给Widget设置了Key时,Flutter根据runtimeType和Key两个条件来判断Widget是否有变化。 |
Flutter中树的复用
3、DOM、Virtual DOM、diff
DOM是什么?
- 文档对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展置标语言的标准编程接口。
- DOM提供了对整个文档的访问模型,将文档抽象成一个倒立生长的树形结构,树的每个节点表示了一种对象类型,节点之间存在父子、兄弟关系。
- DOM是一种与平台和语言无关的应用程序接口(API),可以动态地访问程序和脚本,更新其内容、结构。
- 常见的形式主要有:HTML-DOM、XML-DOM。
常见的HTML-DOM结构

Virtual DOM
Flutter的很多灵感来自于React,比如: Virtual DOM、diff算法、状态管理等。
Virtual DOM的本质
1 | 对应于Flutter,就是在Widget和RenderObject之间做了一个缓存。 |
- Virtual DOM算法主要步骤
1 | 1、用对象结构作为DOM树结构的映射,然后用这个树构建一个真正的DOM树,插到上下文中。 |
diff
- diff作为Virtual DOM算法的核心,具有一定的规则。
1 | 1、深度优先遍历新旧2棵Virtual DOM树,给每个节点设定唯一的标记。 |
问题:对于多个相同TagName或相同runtimeType的DOM节点,如何复用?
答案:给Widget设置Key,保证唯一性。
4、Key的分类及使用
Key的定义
- 官方定义如下:
1
2
3Key是Widget、Element、SemanticsNode(语义节点)的标识符。
只有当新的Widget的Key与当前Element中Widget的Key相同时,它才会被用来更新现有的Element。
Key在具有相同父级的Element之间必须是唯一的。 - Key的作用:diff算法的关键。
Key的分类

- LocalKey
1 | LocalKey直接继承自抽象类Key ,应用于拥有相同父级Element的Widget进行比较的场景。 |
- GlobalKey
1 | GlobalKey直接继承自抽象类Key,内部使用了一个静态常量Map来保存它对应的Element。 |
GlobalKey的源码如下:

Key的使用
5、参考资料
How Flutter renders Widgets:https://www.youtube.com/watch?v=996ZgFRENMs
Flutter‘s Rendering Pipeline:https://www.youtube.com/watch?v=UUfXWzp0-DU
Flutter中的Key:https://www.youtube.com/watch?v=kn0EOS-ZiIc
深度剖析:如何实现一个 Virtual DOM 算法:https://github.com/livoras/blog/issues/13