Flutter Element挂载核心逻辑分析

前言

本篇主要介绍一下关于Flutter中父子Element如何实现挂载,Element树又是怎么样一个一个枝节节点完成搭建。尽管这些基础知识的文章介绍已经很多,但是相信5分钟看完本篇文章后你或许可以更透彻的理解这部分framework的源码逻辑。

本篇文章基于Flutter 3.7.1版本进行源码分析

开始分析

首先,贴一段看起来很...的代码:

我们要知道Flutter树结构是从上向下开始完成构建的,这里说的从上向下,通俗来讲就是先声明的Widget先构建,后声明的后构建。从这段代码片段里来看就是A→B→C,即元素深度是A先于B先于C的;

从这段及其简单及冗余的代码开始...我们先来理解一下这个步骤在framework代码中是怎么实现的;

可能有的朋友会说,这实际上就是一行代码,只是对象构造中的多层嵌套,最终return出去,明明是先执行了C而后是B再后才是A

没错,但是我们要知道,Flutter中每个Widget都可以理解为一个描述文件,传入的child以及其他各种配置参数只有在最终执行build方法的时候才会启动转换挂载;

这里就要追溯一下build方法在哪里被调用

从我前一篇文章中《Flutter中从runApp开始看Element树是如何实现第一次挂载的》可以得知,Flutter应用Element树上挂载的第一颗元素是runApp方法传入的Widget对应的Element,最终进入其Element#mount方法实现挂载,回顾一下mount方法:

这是Element类的mount方法,看起来好像并没有什么值得深入的东西,不过我们要知道Flutter是有多种Element的,这里我们就要看一下mount方法的子类实现

可以看到直接子类、间接子类种有非常多的方法重载;

回到上面的Container嵌套例子,我们就假设现在是ContainerA开始了挂载,来看一下其mount方法都做了什么逻辑,ContainerStatelessWidget,其对应的ElementStatelessElementStatelessElement 没有重写mount方法,其父类ComponentElement 进行了重写:

注意以上的mount_firstBuild方法块是ComponentElement中的,而rebuild方法又回到了Element中,继续看Element类中的performRebuild方法

Flutter 老版本此方法是抽象方法,强制子类实现;新版本(当前3.7.1)在父类中多了_dirty的赋值

不用怀疑,这就是一个要交给子类去重载的方法;

这里回到我们的例子中,我们现在假设上述看到的mount_firstBuildrebuild都是在ContainerA中的逻辑,而Container是一个StatelessWidget,其对应的ElementStatelessElement,而StatelessElement的父类是ComponentElement,我们来看看ComponentElement是如何重载这个父类"空"方法的:

这个方法里主要有两个重要的任务

  1. 调用自己的build方法,获取到一个Widget
  2. updateChild方法(这个方法是Element中的,也就是说这里从ComponentElement回到了父类中),返回值为_child变量赋值(_child是一个Element,是子类的Element

这里我们清楚一点,从我们的例子来解读,此处执行performRebuild的是ContainerAElement,所以这个build就是StatelessElementbuild方法:

这里就是我们熟知的Container类的build方法实现,这里就是对于传入的子Widget(即child)进行包装,然后返回一个Widget,看到这里,也就是我们的ContainerB,实际上就是被包装后赋值给了built变量,然后传入updateChild,这里先不深入,简单解释一下updateChild方法;

关于updateChild简单说明方法

Element类中的方法,主要负责的是更新子类,从方法入参可以看到,传入了_child,又将返回值给了_child,就是说这里面会进行一系列判断:子Element是否加载过?是否仅需更新而非重新构建?等等。无论如何,最终方法内会返回一个Element,并赋值给_child

尽管先不关注updateChild方法中的各种判断,不过有一个重要的点我们需要知道:

如果子Element(也就是我们例子中ContainerB对应的Element)之前不存在,需要重新构建一个,那么会进入其中的inflateWidget方法

这里面有两个我们熟悉的方法,createElementmount

至此,就完成了从ContainerAmountContainerBmount,就是我们的Element树的向下逐个挂载逻辑。


总结

看到这里我们只是看完了ComponentElement挂载的完整逻辑,至于其他类型的Element(比如RenderObjectElement),他们去加载子类Element的流程是不完全一样的,不过核心方法没有区别,最终都会进入它们的顶级父类Element中的updateChildinflateWidget等方法。

本篇先只梳理了ComponentElement的源码逻辑,最后做一下总结:

  • Element类中的mount方法里,只进行了一些通用的赋值操作,加载子类等操作都是在对应子Element的方法重载中
  • 最终加载子Element的核心方法是在顶级父类Element中的updateChildinflateWidget方法中
相关推荐
中微子9 分钟前
React状态管理最佳实践
前端
烛阴19 分钟前
void 0 的奥秘:解锁 JavaScript 中 undefined 的正确打开方式
前端·javascript
中微子25 分钟前
JavaScript 事件与 React 合成事件完全指南:从入门到精通
前端
Hexene...34 分钟前
【前端Vue】如何实现echarts图表根据父元素宽度自适应大小
前端·vue.js·echarts
alexhilton40 分钟前
为什么你的App总是忘记所有事情
android·kotlin·android jetpack
天天扭码1 小时前
《很全面的前端面试题》——HTML篇
前端·面试·html
xw51 小时前
我犯了错,我于是为我的uni-app项目引入环境标志
前端·uni-app
!win !1 小时前
被老板怼后,我为uni-app项目引入环境标志
前端·小程序·uni-app
Burt1 小时前
tsdown vs tsup, 豆包回答一坨屎,还是google AI厉害
前端
群联云防护小杜2 小时前
构建分布式高防架构实现业务零中断
前端·网络·分布式·tcp/ip·安全·游戏·架构