【Flutter】Flutter 组件 ④ ( 组件渲染 的 三棵树理论 | Widget 树 → Element 树 → RenderObject 树 )

文章目录

  • [一、组件渲染 的 三棵树理论 ( Widget 树 → Element 树 → RenderObject 树 )](#一、组件渲染 的 三棵树理论 ( Widget 树 → Element 树 → RenderObject 树 ))
    • [1、Widget 树](#1、Widget 树)
      • [Widget 分类](#Widget 分类)
      • [Widget 本质体现](#Widget 本质体现)
      • 核心特点
    • [2、Element 树](#2、Element 树)
    • [3、 RenderObject 树](#3、 RenderObject 树)
  • 二、三者之间的关系
  • [三、StatelessWidget 体系中的三棵树](#三、StatelessWidget 体系中的三棵树)
    • [1、StatelessWidget 基础概念](#1、StatelessWidget 基础概念)
    • [2、StatelessWidget 运行流程](#2、StatelessWidget 运行流程)
    • [3、StatelessWidget 运行流程](#3、StatelessWidget 运行流程)
    • [4、StatelessWidget 基础代码结构分析](#4、StatelessWidget 基础代码结构分析)
    • [5、StatelessWidget 运行流程](#5、StatelessWidget 运行流程)
    • [6、BuildContext 参数](#6、BuildContext 参数)
    • [7、StatelessWidget 生命周期与三棵树对应关系](#7、StatelessWidget 生命周期与三棵树对应关系)
      • [阶段 1 : 组件被创建 ------ 构造函数执行](#阶段 1 : 组件被创建 —— 构造函数执行)
      • [阶段 2 : 组件被挂载 ------ Framework 暗箱操作](#阶段 2 : 组件被挂载 —— Framework 暗箱操作)
      • [阶段 3 : 首次绘制界面 ------ build () 执行](#阶段 3 : 首次绘制界面 —— build () 执行)
      • [阶段 4 : 界面更新 ------ build () 再次执行](#阶段 4 : 界面更新 —— build () 再次执行)
      • [阶段 5 : 组件销毁 ------ 移除树](#阶段 5 : 组件销毁 —— 移除树)
  • [四、StatefulWidget 体系中的三棵树](#四、StatefulWidget 体系中的三棵树)
    • [1、StatefulWidget 概念](#1、StatefulWidget 概念)
    • [2、StatefulWidget 代码组成](#2、StatefulWidget 代码组成)
    • [3、StatefulWidget 生命周期与三棵树对应关系](#3、StatefulWidget 生命周期与三棵树对应关系)
    • [4、初始化阶段 与 更新阶段 生命周期 汇总对照表](#4、初始化阶段 与 更新阶段 生命周期 汇总对照表)

参考文档 :

一、组件渲染 的 三棵树理论 ( Widget 树 → Element 树 → RenderObject 树 )


关系链条 : Widget ( 描述 ) → 创建 / 更新 → Element ( 实例 ) → 创建 / 更新 → RenderObject ( 渲染 )

树名称 身份定位 核心作用 生命周期
Widget 树 配置文件 / 蓝图 描述页面长什么样 ( 数据 + 结构 ) 极短 , 每次 build 都会新建
Element 树 中间代理人 / 实例 持有 Context 和 State , 管理生命周期 长 , 尽量复用、不轻易销毁
RenderObject 树 渲染实体 真正 测量、布局、绘制 到屏幕 最长 , 必须复用 , 性能开销最大

把 Flutter 页面当成一栋房子 , 三棵树 分工不同、层层协作 :

  • Widget 树 = 整套设计图纸 , 描述 UI 长啥样 , 频繁更新、不可变 ;
  • Element 树 = 现场施工管理员 ( 工长 ) , 中间调度、状态载体、优先复用 , 三树核心 ;
  • RenderObject 树 = 实际砌好的墙体 / 门窗 / 家具 ( 真实建筑 ) , 负责布局、绘制、交互 , 最终展示 ;

页面从无到有、刷新更新 , 就是 " 图纸 → 工长指挥 → 建成实物 " 的过程 ;

1、Widget 树

Widget 是 描述层 , 描述配置 / 模板 , 其 本质 是 不可变的 " 配置描述类 " , 是纯数据 , 无渲染能力 ;

  • 不可变 : Widget 是不可变的 ( immutable ) 纯数据类 , 其 属性全部是 final 修饰的 , 一旦创建不能修改 ;
  • 不修改 : 状态变化不会修改旧 Widget , 而是 新建 Widget ;
  • 图纸 : Widget 不是真正的页面 , 只是 描述页面应该长什么样的配置信息 ;
  • 重建: 每次 调用 build() 方法 , 都会 重新创建一个新的 Widget ;
  • 轻量 : 一个 Widget 只有几十个字节 , 重建对性能无压力 ;
  • 无状态 : 不存储数据 , 不管理生命周期 , 不负责渲染 ;

每次 setState 或者 父组件重建 , 大概率会新建一批 Widget 实例 ;

Widget 分类

所有 Widget 继承自 DiagnosticableTreeWidget , 分两大类 :

  • StatelessWidget : 无 可变状态 , build 只依赖入参 ;
  • StatefulWidget : 携带 可变状态 , 自身依然不可变 ;

Widget 本质体现

Widget 树 是 设计图纸 , 只写规则 , 不是实物 ;

代码中写的 Container、Text、Button、Column 等所有组件代码 , 层层嵌套 , 组成了 Widget 树 ;

下面的 嵌套组件 代码 就是 Widget 树 ;

dart 复制代码
// 这只是一份配置 ; 每次 build 都会生成新的 Scaffold/Text 实例
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
		Column(
		  children: [
		    Text("你好"),
		    ElevatedButton(child: Text("点击"))
		  ]
		)
    ),
  );
}

核心特点

Widget 树 核心特点 :

  • 描述信息 : 纯描述 , 静态、不可修改 ;
  • 建筑图纸 : 只画样式、大小、颜色、位置、用什么材料 , 图纸本身不能住人、不能用 ;
  • 复用规则 : 每次页面刷新、setState , 都会 重新生成一版新图纸 ( 新 Widget ) , 旧图纸直接丢掉 ;
  • 渲染能力 : 没有渲染能力 , 自己没法显示在屏幕上 ;

2、Element 树

Element 树 是 实例层 , 是 挂载 & 调度中枢 , 是 Widget 的 " 实例代理 " , 框架内部核心调度单元 , 是 连接 Widget 和 RenderObject 的 桥梁 ;

  • 实体对象 : 代码中的 BuildContext 的 本质就是 Element 树的实体对象 ;
  • 创建 Element : 在 Framework 层 调用 Widget.createElement() 函数创建 , 对应 Element 树 , 长期存活 ( 页面不销毁就尽量复用 ) ;
  • Element 分类 对应 Widget :
    • StatelessElement → 绑定 StatelessWidget
    • StatefulElement → 绑定 StatefulWidget + 持有 State
  • Element 是 Widget 的实例化对象 , 是三棵树的核心中枢 ;
  • Element 持有 Widget 配置 , 同时持有 State 状态 , 还持有对应的 BuildContext ;
  • 跨页面、跨组件共享的状态、GlobalKey , 全都存在 Element 里 ;
  • 它负责 对比新旧 Widget , 决定 是否复用 / 创建 RenderObject ;
dart 复制代码
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
    );
  }
}

通俗理解

Element 树 是 现场工长 , 负责 中间调度、核心枢纽 ;

Flutter 拿到 Widget ( 图纸 / 代码 ) 后 , 自动 为每一个 Widget 生成一个对应的 Element , 层层嵌套形成 Element 树 ;

Element 树 是 图纸 ( Widget 树 ) 和 实物 ( RenderObject 树 ) 之间的中间人 , 也是三棵树里生命周期最长、最核心的一环 ;

核心特点

Element 树 核心特点 :

  • 绑定关系 : 一张图纸 (Widget) → 对应一位工长 (Element) ;
  • 复用规则 : Element 树 能复用 , 不随便销毁 ;
    • 重建时机 : 图纸换新 ( Widget 重建 ) , 只要结构差别不大 , 工长继续留任 , 不用换人 ;
    • 高性能 : 这就是 Flutter 性能高的关键 , 不反复创建销毁管理人员 ;

主要工作

Element 树 主要工作 :

  • BuildContext 就是 Element : 在组件里用的 context , 本质就是当前组件的 Element ;
  • 持有对应 Widget 引用 , 触发 build() 方法 , 生成子 Widget , 拿着最新图纸 , 指挥下一步施工 ;
  • 树的结构固定 : Element 树结构 一旦生成 , 不会轻易改变 ( Widget 重建但 Element 复用 ) ;
  • 树更新时做 Diff 比对 ( 核心优化逻辑 ) , 决定复用 / 新建 Element , 对比新旧图纸差异 ( Diff ) , 判断要不要改实物 ;
  • 管理组件状态 ( State 就挂在它身上 ) , 持有 State , StatefulWidget 状态 , 全部存在 Element 中 ;
  • 挂载到 RenderObject , 驱动渲染 , 对接下层真正负责显示的 RenderObject ;
  • 管理生命周期 : create → update → deactivate → dispose ;

与 GlobalKey 的关系

Element 与 GlobalKey 的关系 :

  • GlobalKey 会把 Element 注册到全局 Map 中 ;
  • 通过 GlobalKey 直接拿到 Element , 进而拿到 State/Context , navigatorKey.currentContext 能全局获取上下文 ;
dart 复制代码
// 定义全局导航键 , 用于在无上下文环境下全局跳转页面
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

// 应用根组件 , 无状态组件
class MyApp extends StatelessWidget {
  // 构造函数 , 传递父组件的 key
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    // 构建应用根页面 , MaterialApp 是 Material 风格应用入口
    return MaterialApp(
      // 绑定全局导航键 , 实现全局路由跳转
      navigatorKey: navigatorKey,
    );
  }
}

State 可变数据

State 是 可变数据 , 是 StatefulWidget 的 可变状态容器 , 只被 StatefulElement 持有 , StatelessElement 不持有 State 状态数据 ;

State 仅存在于 StatefulWidget 体系 , StatelessWidget 无 State ;

State 关键特性 :

  • 可变 : 内部变量可以修改 , 是页面交互、数据变化的载体 ;
  • 依附 Element 存活 : State 的生命周期 = 绑定的 Element 生命周期 ;
  • setState() : 标记当前 Element 为脏节点 , 触发局部重建 ;

比喻 : State = 房间里的家具 / 电器状态 ( 灯亮不亮、计数器数字 ) , 会随使用变化 ;

3、 RenderObject 树

RenderObject 树 负责 屏幕上的「像素渲染」, 是 真正负责 把内容画到屏幕上 的 实体 ;

  • Widget 是图纸 , Element 是 工长 , RenderObject 是 真实建筑 , 也就是 最终显示在屏幕的东西 ;
  • 唯一真正 参与绘制、布局、点击交互的一层 ;
  • 屏幕上看到的文字、按钮、颜色、大小、点击响应 , 全靠它实现 ;
  • 负责三件事 : 布局 ( 算大小位置 ) 、绘制 ( 上色画图 ) 、事件响应 ( 点击 / 滑动 ) ;
  • 尽量复用 : RenderObject 性能开销最大 , Flutter 会尽可能复用它 , 避免重建 , 界面小改动时 , 不会推倒重建 , 只进行局部修改 ;

Element ( 工长 ) 根据图纸 , 最终 指挥 搭建出真正能看见、能触摸、有尺寸位置的界面 , 所有渲染节点组合起来 , 就是 RenderObject 树 ;

二、三者之间的关系


1、三棵树对应关系

三棵树固定绑定规则 :

  • 一个 Widget 对应一个 Element : Element 由 Widget 生成 , 初始一一对应 ;
  • 一个 StatefulElement 唯一持有一个 State ;

2、三棵树完整运转流程

页面首次渲染运转流程 : 代码写图纸 → 工长来对接 → 渲染真实界面 ;

  • 创建 Widget : 编写代码 → 生成 Widget 树 ( 图纸 ) , 开发者编写的 Widget 树 被构建 ;
  • 创建 Element : Flutter 解析图纸 / 遍历 Widget → 为每个 Widget 创建 Element ( 工长 ) , 形成 Element 树 , Framework 调用 widget.createElement() → 生成 Element ;
  • 创建 RenderObject : 渲染 " 界面 " , 每个 Element 按照图纸要求 → 创建对应的 RenderObject ( 实体界面 ) , 形成 RenderObject 树 , RenderObject 完成 测量、布局、绘制 → 最终显示在手机 / 电脑屏幕上
dart 复制代码
Widget ( 新 )  → 创建 Element → 创建 RenderObject → 渲染显示

页面刷新流程 :

  • 触发刷新 : 点击按钮 → 调用 setState() 方法 , 触发 Widget 树重新构建 ;
  • 新建 Widget : Flutter 重新生成新的 Widget 树 ( 新版图纸 ) ;
  • 对比 Element : Flutter 用新 Widget 对比 旧 Element 持有的旧 Widget , 相当于 原有 Element 树 ( 工长们 ) 不换人 , 拿着新旧两张图纸做对比 :
    • 结构、类型、Key 没有改变 , 只是文字 / 颜色改了 → Element 继续复用 / 继续用原工长、原状态 , 用新 Widget 配置更新自己 ;
    • 组件结构增删、位置调换 / Key 发生改变 → 调整对应工长分工 ;
  • 复用 RenderObject : 工长指挥 RenderObject 局部更新 , 只改变化的地方 , 不全量重建 ;
  • 屏幕刷新 : 看到最新效果 ;
dart 复制代码
Widget ( 新 )  → 复用 Element ( 更新配置 )  → 复用 RenderObject ( 刷新绘制 ) 

3、页面重建刷新逻辑特点

页面重建刷新逻辑特点 :

  • Widget 最爱换新 : 频繁重建 , 每次都创建新的 Widget 树 ;
  • Element 尽量复用 : Element 和 State 优先复用 , 不会随意销毁结构没变就复用 , 结构变了创建新的 Element 实例 ;
  • RenderObject 必须复用 : 只进行局部修改 , 最小成本刷新 , 全局唯一 , 只进行局部更新 , 不重建 ;

4、页面 首次渲染 与 后续更新 流程

页面首次渲染流程 :

  • 编写 Widget ( 代码定义 )
  • Framework 调用 widget.createElement() → 生成 Element
  • StatefulElement 内部调用 widget.createState() → 生成 State
  • Element 执行 build() → 生成下一级子 Widget
  • 递归直到叶子节点 , 创建 RenderObject 树 → 最终渲染到屏幕

页面更新流程 : 状态更新链路 ( setState 流程 )

  • 调用 state.setState(() { 修改变量 })
  • 标记所属 StatefulElement 为 " 脏节点 " ;
  • 下一帧到来 , Framework 只重建脏节点及其子树 ;
  • Element 重新 build → 生成新的子 Widget
  • 新旧 Widget 做 Diff , 决定复用/新建 Element
  • 页面刷新 , 展示最新状态

三、StatelessWidget 体系中的三棵树


1、StatelessWidget 基础概念

StatelessWidget 中的 Stateless = State ( 状态 ) + less ( 无 ) → 无状态组件

  • StatelessWidget 是静态组件 , 一旦创建 , 界面就不会自动改变 ;
  • 没有 setState() 函数 , 无法主动刷新自己 ;
  • 数据只能通过构造函数传参传入 , 内部不能修改数据 ;

StatelessWidget 适用场景 :

  • 纯展示 UI , 标题、文本、图标、按钮、静态布局 ;
  • 不需要动态修改数据的组件 ;
  • 作为页面的静态结构骨架 ;

2、StatelessWidget 运行流程

StatelessWidget 运行流程 :

  • 创建 StatelessWidget 实例
  • 执行 build(BuildContext context) 函数 ;
  • 返回 Widget 树 ( 界面配置 )
  • Flutter 创建 Element + RenderObject
  • 渲染到屏幕 ( 静态显示 , 无法自动更新 )

3、StatelessWidget 运行流程

StatelessWidget 对应三棵树 : Widget ( 配置 ) + Element ( 实例 ) , 无 State 存储 ;

StatelessWidget 体系 的 结构 是 StatelessWidget → StatelessElement ;

没有 State , 页面内容完全由外部入参决定 ;

父组件重建、自身入参变化 → Element 重新执行 build() ;

BuildContext 的 本质就是 当前 Element 的对外接口 , 用来向上查找父组件、主题、路由等 ;

4、StatelessWidget 基础代码结构分析

基础代码结构 : StatelessWidget 是抽象类 , 强制要求子类重写 build 方法 , 否则直接编译报错 ;

dart 复制代码
// 1. 继承 StatelessWidget
class MyTextWidget extends StatelessWidget {
  // 2. 外部传入的参数 ( 必须 final , 不可变 ) 
  final String text;
  final Color color;

  // 3. 构造函数 ( 接收参数 ) 
  const MyTextWidget({super.key, required this.text, required this.color});

  // 4. 必须重写的 build 方法
  @override
  Widget build(BuildContext context) {
    // 5. 返回一个 Widget ( 渲染到屏幕的内容 ) 
    return Text(
      text,
      style: TextStyle(color: color, fontSize: 20),
    );
  }
}

5、StatelessWidget 运行流程

@override Widget build(BuildContext context) 是 StatelessWidget 组件的灵魂 , build 函数是 Flutter 用来获取组件界面配置的方法 , 它的唯一使命是 描述当前组件要渲染的 Widget 树结构 , 该函数有四个核心作用 :

  • 提供界面蓝图 : 告诉 Flutter 「这个组件要显示什么」 ;
  • 接收上下文 context : 获取当前组件的位置、主题、路由、父组件信息 ;
  • 构建子 Widget 树 : 把你写的 UI 代码 ( Text/Row/Column ) 组合成树 ;
  • 返回渲染配置 : 给 Flutter 提供一份 Widget 配置 , 用于生成 Element/RenderObject ;

build 函数的执行时机 :

  • 组件首次创建 ( 页面加载 ) ;
  • 父组件重建 ( 父组件刷新 , 子组件会跟着执行 build ) ;
  • 依赖的 InheritedWidget 变化 ( 如主题、语言切换 ) ;

build 函数注意事项 :

  • build 函数 必须是纯函数 ( 无副作用 ) ;
  • 不能在里面做网络请求、定时器、耗时操作 ;
  • 不能在里面调用 setState ( Stateless 也没有 ) ;
  • 只做界面描述 , 不做业务逻辑 ;

6、BuildContext 参数

参数 BuildContext context 是 当前组件在 Element 树中的位置引用 , 其本质就是当前组件的 Element , 每个 Widget 的 build 方法都有自己独立的 context , 互不干扰 , 其有如下作用 :

  • 获取主题 Theme.of(context) ;
  • 获取导航 Navigator.of(context) ;
  • 获取媒体查询 MediaQuery.of(context) ;
  • 向上查找父组件 / 状态 ;
  • 标记当前组件在树中的位置 ;

7、StatelessWidget 生命周期与三棵树对应关系

阶段 StatelessWidget 生命周期 三棵树对应动作 核心本质
1. 实例化 构造函数 Constructor 创建 Widget 配置 生成一份界面蓝图
2. 挂载 无 ( Framework 执行 ) 创建 Element 实例 生成代理人 , 持有 Widget
3. 渲染 build () 方法 创建 RenderObject 生成渲染实体 , 显示到屏幕
4. 更新 再次执行 build () 更新 Element / RenderObject 复用实例 , 仅刷新配置
5. 销毁 无 ( Framework 执行 ) 卸载 Element 从树中移除 , 回收内存

阶段 1 : 组件被创建 ------ 构造函数执行

执行 Constructor 构造函数 , 创建 Widget 实例 ,

dart 复制代码
// 1. 调用构造函数 , 创建 Widget 实例
MyWidget(title: "Hello");

对应的三棵树状态 :

  • Widget 树 : 新建一个 MyWidget 配置对象 ;
  • Element 树 : 尚未创建 ;
  • RenderObject 树 : 尚未创建 ;

阶段 2 : 组件被挂载 ------ Framework 暗箱操作

对开发者透明 , 开发者感知不到 , 没有应用层代码执行 , Flutter 框架自动做了 2 件大事 :

  • Element 树 : 调用 MyWidget.createElement() , 创建对应 Element , 这个 Element 会 持有刚才创建的 Widget 引用 ;
  • RenderObject 树 : 尚未创建 ;

阶段 3 : 首次绘制界面 ------ build () 执行

执行 build() 函数 , 给 Element 提供最新的 Widget 配置 , 让 Element 去生成渲染实体 , 创建的 Element , 就是 build 函数的 BuildContext 参数 ;

dart 复制代码
@override
Widget build(BuildContext context) {
  // 3. 描述 UI
  return Text(title); 
}

对应的三棵树状态 :

  • Widget 树 : build 方法 返回新的子 Widget ( Text ) ;
  • Element 树 : Element 调用 mount 方法 , context 就是这个 Element ;
  • RenderObject 树 : Element 触发 createRenderObject , 创建真正的渲染对象 , 测量、布局、绘制 , 显示到屏幕 ;

阶段 4 : 界面更新 ------ build () 再次执行

父组件 调用 setState 函数后 , 重新创建了 MyWidget ;

dart 复制代码
// 父组件 setState 后 , 重新创建了 MyWidget
MyWidget(title: "Hello Updated"); 

对应的三棵树状态 : 页面刷新过程中 , Widget 重建了 N 次 , Element 和 RenderObject 始终是同一个 ( 复用 ) , 这就是 Flutter 流畅的原因 ;

  • Widget 树 : 全新创建一个 MyWidget ( 配置变了 ) ;
  • Element 树 : 复用旧 Element ; 框架对比 Key 和类型 , 发现类型没变 , 不新建 Element ; 如果发生了变化 , 则调用 update 方法 , 用新 Widget 替换掉 Element 持有的旧 Widget ;
  • RenderObject 树 : 复用旧 RenderObject , Element 通知 RenderObject 更新属性 ( 文字、颜色等 ) , 不重建 ;

阶段 5 : 组件销毁 ------ 移除树

用户感知不到 , 没有表层代码运行 ;

对应的三棵树状态 :

  • Element 树 : Element 从树中移除 ( Unmount ) ;
  • RenderObject 树 : 随之销毁 ;
  • Widget 树 : 配置对象被垃圾回收 ;

四、StatefulWidget 体系中的三棵树


1、StatefulWidget 概念

StatefulWidget = 有状态的组件 ( 可以动态刷新 UI ) , 有可变状态 State , 可以 通过 setState() 主动刷新界面 , 适用于 表单、地图、动画、计时器、视频、交互页面 场景 ;

由 Widget ( 壳 ) + State ( 核心 ) 组成 , Widget 是临时配置 不可变 , State 是永久状态 可变 , 状态存在 State 里 , 不是 Widget 里 , setState () 会触发 build () 重绘 ,

Widget 是临时配置 ( 随时销毁重建 ) , State 是永久状态 ( 不销毁 ) ;

真正管理数据、逻辑、生命周期、刷新的是 State , 不是 Widget

2、StatefulWidget 代码组成

StatefulWidget 由两部分组成 :

  • 外部 Widget : 壳 , 不可变 , 只接收参数、创建 State ;
    • 必须继承 StatefulWidget ;
    • createState() → 生命周期最重要方法 , 执行 1 次 , 创建并返回 State 实例 , State 会被 Element 持有 , 永久存活 ;
dart 复制代码
class 组件名 extends StatefulWidget {
  const 组件名({super.key});
  
  @override
  State<组件名> createState() => 组件State();
}
  • 内部 State : 是 核心 , 存储 State 状态 + 逻辑 , 是真正的 组件本体 , 存变量、写逻辑、生命周期、build 函数 ;
dart 复制代码
class 组件State extends State<组件名> {
  // 状态变量
  int count = 0;

  @override
  void initState() {} // 初始化

  @override
  Widget build(BuildContext context) {} // 构建UI

  @override
  void dispose() {} // 销毁
}

基础代码结构示例 :

dart 复制代码
// 1. Widget 本身不可变
class Counter extends StatefulWidget {
  const Counter({super.key});

  // 2. 由 Element 调用 , 创建 State ( 只执行一次 , Element 不复用才会重走 ) 
  @override
  State<Counter> createState() => _CounterState();
}

// 3. 可变状态类
class _CounterState extends State<Counter> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () => setState(() => count++),
      child: Text("计数: $count"),
    );
  }
}

3、StatefulWidget 生命周期与三棵树对应关系

StatefulWidget 三棵树 : StatefulWidget → 生成 StatefulElement → Element 同时持有 Widget + State ;

  • Widget : 不可变配置蓝图 , 每次重建都会生成新实例 , 轻量 ;
  • Element : 组件运行实例 , 三棵树中枢 , 永久持有 State 对象 , 组件不卸载就不会销毁 ;
  • RenderObject : 渲染实体 , 负责测量、布局、绘制 , 开销最大 , 框架优先复用 ;

执行顺序 : StatefulWidget 构造函数 → createState() → Element 创建挂载 → initState() → didChangeDependencies() → build() → RenderObject 创建 ;

State 完整生命周期 ( 按执行顺序 ) :

  • StatefulWidget 构造函数 : 创建 StatefulWidget 实例 , 仅传递配置 , 无状态存储 , 属性均为 final ;
    • Widget 树 : 新增一个 SurfaceMapView 配置节点 ;
    • Element 树 / RenderObject 树 : 尚未创建 ;
  • ① createState() : 创建 State 状态数据 , 令 Element 持有 State ; @override State<SurfaceMapView> createState() => SurfaceMapViewState(); , Widget 树 : 无变化 ; Element 树 : 框架根据 Widget 类型 , 创建 StatefulElement ; 该 Element 内部强引用绑定 : 当前 Widget + 刚创建的 State ; RenderObject 树 : 仍未创建 ;
    • 所属 : StatefulWidget 方法 ;
    • 时机 : Element 首次创建时调用 , 全局仅执行 1 次 ;
    • 特点 : Element 不复用才会再次执行 , Element 复用则永远不执行 ;
    • State 状态 : State 从诞生起就被 Element 持有 , 后续无论 Widget 重建多少次 , State 始终不变 , 这是状态不丢失的根源 ;
  • Element 执行 mount 挂载 : 在 createState() 与 initState() 之间执行 , 无暴露方法 , 框架内部执行逻辑 , 开发者无感知 ;
    • Element 正式接入整棵 Element 树 , 确立父子层级、上下文关系 ;
    • 代码里 build(BuildContext context) 中的 context , 就是这个 Element 本身 ;
  • ② initState() : 初始化 State 状态数据 , Element 挂载 ; 三棵树结构已就位 , 但 RenderObject 还未生成 ; 当前仅在 Element + State 层做业务初始化 , 不触发渲染 ;
    • 时机 : Element 挂载到树中 , 只执行一次
    • 用途 : 初始化数据、订阅事件、网络请求、动画初始化
    • 限制 : 内部不能调用 setState ( 节点还未就绪 )
  • ③ didChangeDependencies() : 该阶段 Element 检测上层依赖变化 , 更新自身上下文环境 , 不重建任何树节点 ;
    • 时机 : initState 之后、首次 build 之前执行 ; 当组件依赖的 InheritedWidget ( 主题、语言、路由、全局配置 ) 变化时 , 也会重复触发 ;
    • 场景 : 主题切换、语言切换
  • ④ build() : 第一次界面构建 , 渲染 CustomPaint + 手势 + 布局 , Widget 生成 → Element 更新 → RenderObject 渲染 ;
    • 时机 : 初始化 + setState + 依赖变化 + 父组件重建 都会触发 , 每次调用 setState 都会执行 ;
    • 核心 : 构建 UI 结构 ; 不要在其中执行耗时操作 ;
    • 三棵树状态 : 初始化完成后 , 三棵树全部成型 , 结构固定 , 后续优先复用 ;
      • Widget 树 : build 返回一整棵子 Widget 树 ( 布局、手势、绘制组件 ) ;
      • Element 树 : 遍历返回的子 Widget , 批量创建对应子 Element , 完善整棵 Element 树 ;
      • RenderObject 树 : Element 调用 createRenderObject , 首次创建渲染对象 , 执行测量、布局、绘制 , 地图画布正式显示在屏幕 ;
  • ⑤ didUpdateWidget(Widget oldWidget) :
    • 时机 : 新旧 Widget 替换 , 但 Element 被复用时 ( 同级 Widget 更新、未重建 Element )
    • 场景 : 父组件重建 , 传入新参数 , Element 保留 , 更新配置
  • ⑥ deactivate() : 组件即将从当前树移除时触发 ; 如果只是临时移动树位置 ( 路由跳转复用组件 ) , 后续会重新挂载 ;
    • 时机 : Element 从树中临时移除 ( 页面跳转、组件移出可视区 )
    • 临时过渡阶段 , 不一定销毁 ;
    • 三棵树状态 : Element 临时脱离原有树结构 , RenderObject 暂时保留 ;
  • ⑦ dispose() : 释放绘制资源 ;
    • 时机 : Element 永久从树中移除 ( 页面销毁、组件彻底卸载 )
    • 用途 : 取消定时器、取消网络监听、释放资源、关闭流 ;
    • 特点 : 执行后 State 彻底失效 , 不能再调用 setState ;

运行时更新 生命周期 :

  • 调用 setState() 函数 触发 运行时更新 : 内部调用 setState () 函数 , 执行流程 : 调用 setState() 函数 → Element 标记为脏节点 → build () → 更新渲染 ; 对应的 三棵树 状态 :
    • Widget 树 : 原有 Widget 实例不变 ;
    • Element 树 : 当前 Element 被标记为 Dirty 脏节点 , 框架在下一帧会统一处理脏节点 , 不会立即刷新 ;
    • RenderObject 树 : 暂时无动作 ;
  • 再次执行 build () 函数 : 重新构建子 Widget 树 ; 三棵树状态 :
    • Widget 树 : 重新生成全新的子 Widget 实例 ( 轻量 , 无性能压力 ) ;
    • Element 树 : 完全复用原有 Element ; 框架对比新旧 Widget ( 类型 + Key ) , 匹配成功 , 调用 update 方法 , 让 Element 持有新的 Widget 配置 ; Element、State 全程不销毁、不重建 ;
    • RenderObject 树 : 复用原有 RenderObject ; 仅根据新 Widget / 新状态更新绘制属性 ( 画布大小、图形位置、缩放比例 ) , 不重建渲染对象 ;
dart 复制代码
createState() → initState() → didChangeDependencies() → build()
                ↓
        【运行中】
  setState() / 依赖变化 → build()
  父组件更新、Widget 替换 → didUpdateWidget() → build()
                ↓
        组件移除
        deactivate() → dispose()

4、初始化阶段 与 更新阶段 生命周期 汇总对照表

初始化阶段 生命周期 : 只走一次 ;

  • StatefulWidget() 构造函数 → 创建 Widget 配置
  • createState() → 创建 State 实例
  • initState() → 初始化数据、监听、控制器
  • didChangeDependencies() → 处理依赖 ( 主题 / 路由 )
  • build() → 第一次构建界面

更新阶段 生命周期 : 执行很多次 ;

  • setState() → 触发刷新
  • didUpdateWidget() → 父组件刷新传递新 Widget
  • build() → 重新构建界面

销毁阶段 生命周期 : 只执行一次 ;

  • dispose() → 释放资源 ( 控制器、监听 )
  • State 销毁
生命周期方法 执行时机 Widget 树 Element 树 RenderObject 树
Widget 构造函数 组件首次创建 新建节点
createState() 构造后立即执行 不变 创建 StatefulElement , 绑定 State
initState() Element 挂载后 , 首次 build 前 不变 Element 已挂载 , 状态初始化
didChangeDependencies() 依赖变更 /initState 后 不变 更新依赖上下文
首次 build () 初始化阶段最后一步 生成子 Widget 树 创建子 Element 树 首次创建 RenderObject
setState() 主动更新状态 不变 Element 标记为脏节点 不变
重复 build () 刷新 UI 重建子 Widget 复用原有 Element 复用 RenderObject , 仅更新属性
didUpdateWidget() 父组件传入新 Widget 替换旧 Widget 复用 Element+State 不变
deactivate() 组件即将移出树 不变 Element 临时脱离树 不变
dispose() 组件永久销毁 节点回收 Element 彻底销毁 RenderObject 销毁回收
相关推荐
心中有国也有家7 小时前
使用 DevEco Studio 配置 Flutter 鸿蒙签名
学习·flutter·华为·harmonyos
nice先生的狂想曲8 小时前
Stream与StreamController以及对应的使用场景
flutter·客户端
心中有国也有家8 小时前
鸿蒙Flutter开发环境从零搭建教程(Windows/macOS双平台·避坑版)
学习·flutter·华为·harmonyos
心中有国也有家11 小时前
Flutter 鸿蒙适配第一步:从 hive 迁移到 hive\_ce
hive·学习·flutter·华为·harmonyos
心中有国也有家11 小时前
AtomGit Flutter 鸿蒙客户端:E-Brufen 架构设计
学习·flutter·华为·harmonyos
心中有国也有家13 小时前
鸿蒙 Flutter 本地存储实战:Hive CE 从入门到精讲
人工智能·hive·flutter·华为·harmonyos
程序员老刘1 天前
老刘给大家道个歉
flutter·ai编程
binbin_521 天前
Flutter 开发鸿蒙实战:Windows 环境下从 HAP 构建到四 Tab 页面运行
windows·flutter·harmonyos
阳光予你1 天前
Flutter 字体渲染与字重
flutter
恋猫de小郭2 天前
Flutter 开发怎么做 Agent ?从工程实战详细给你解读下
android·前端·flutter