
概述这篇博客我们主要是结合demo介绍下Element的生命周期函数。1. 普通的StatefulWidget我们开发中接触最多的StatefulWidget组件通过setState方法刷新数据。State的回调是对StatefulElement生命周期的封装Element 本身的生命周期mount→update→deactivate→activate→unmount(dispose)我们无法直接重写框架内部 Element我们通过 State 的回调 GlobalKey 拿 Element 自定义 Element 子类 Demo 两种方式验证。1.不复用Element我们在开发过程中接触最多的StatefulWidget为例。import package:flutter/material.dart; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); override Widget build(BuildContext context) { return MaterialApp( home: const HomePage(), ); } } class HomePage extends StatefulWidget { const HomePage({super.key}); override StateHomePage createState() _HomePageState(); } class _HomePageState extends StateHomePage { bool showChild true; override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text(Element生命周期Demo)), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () { setState(() { showChild !showChild; }); }, child: const Text(添加/移除子组件), ), const SizedBox(height: 30), if (showChild) const LifeDemoChild(), ], ), ), ); } } class LifeDemoChild extends StatefulWidget { const LifeDemoChild({super.key}); override StateLifeDemoChild createState() _LifeDemoChildState(); } class _LifeDemoChildState extends StateLifeDemoChild { override void initState() { super.initState(); debugPrint(✅ initState —— Element mount 阶段只执行一次); } override void didChangeDependencies() { super.didChangeDependencies(); debugPrint( didChangeDependencies —— mount / InheritedWidget变化触发); } override void didUpdateWidget(covariant LifeDemoChild oldWidget) { super.didUpdateWidget(oldWidget); debugPrint( didUpdateWidget —— Element update复用时触发不会走mount); } override void deactivate() { super.deactivate(); debugPrint(⚠️ deactivate —— Element脱离树可能还会被GlobalKey复用); } override void activate() { super.activate(); debugPrint( activate —— Element重新激活GlobalKey复用场景); } override void dispose() { super.dispose(); debugPrint(❌ dispose —— Element unmount彻底销毁); } int buildCount 0; override Widget build(BuildContext context) { buildCount; debugPrint(️ build 执行buildCount $buildCount); return const Text(我是子组件); } }1.页面打开页面打开之后控制台输出日志如下✅ initState —— Element mount 阶段只执行一次 didChangeDependencies —— mount / InheritedWidget变化触发 ️ build 执行buildCount 12. 点击按钮移除组件控制台输出如下日志✅ initState —— Element mount 阶段只执行一次 didChangeDependencies —— mount / InheritedWidget变化触发 ️ build 执行buildCount 1此时组件彻底销毁再次点击是全新Element重新走initState。3.再次点击增加组件的时候这个时候重新执行initState、didChangeDependencies函数。2.验证Element update(复用Element不会执行 initState)关键点key 不变类型不变canUpdatetrueElement 复用执行 didUpdateWidget不会执行 initState 修改 HomePage给子组件传一个动态 countkey 保持不变在上面的demo中当我们点击删除和增加子组件的时候不会复用Elment这里我们看一复用Element的例子。class HomePage extends StatefulWidget { const HomePage({super.key}); override StateHomePage createState() _HomePageState(); } class _HomePageState extends StateHomePage { int count 0; override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text(update复用Element Demo)), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () { setState(() { count; }); }, child: const Text(父setState更新), ), const SizedBox(height:20), // key固定canUpdate返回trueElement复用 LifeDemoChild(key: const ValueKey(fixedKey), count: count), ], ), ), ); } } class LifeDemoChild extends StatefulWidget { final int count; const LifeDemoChild({super.key, required this.count}); override StateLifeDemoChild createState() _LifeDemoChildState(); } class _LifeDemoChildState extends StateLifeDemoChild { override void initState() { super.initState(); debugPrint(✅ initState 只mount执行一次); } override void didUpdateWidget(covariant LifeDemoChild oldWidget) { super.didUpdateWidget(oldWidget); debugPrint( didUpdateWidget 旧count:${oldWidget.count}, 新count:${widget.count}); } override Widget build(BuildContext context) { debugPrint(️ build count${widget.count}); return Text(子组件 count ${widget.count}); } }点击按钮父 setState不会打印 initStateElement 被复用不走mount触发didUpdateWidget然后 build。flutter: didUpdateWidget 旧count:1, 新count:2flutter: ️ build count2如果修改代码每次给不同 keyValueKey(count)canUpdate 返回 false每次销毁重建会反复打印 initState。3.GlobalKey 验证deactivate activate重点deactivate 之后不 dispose再次挂载执行activate()不会走 initStateimport package:flutter/material.dart; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); override Widget build(BuildContext context) { return MaterialApp(home: GlobalKeyDemo()); } } class GlobalKeyDemo extends StatefulWidget { override StateGlobalKeyDemo createState() _GlobalKeyDemoState(); } class _GlobalKeyDemoState extends StateGlobalKeyDemo { final GlobalKey_ChildState globalKey GlobalKey(); bool showA true; override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text(GlobalKey复用Element Demo)), body: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () { setState(() { showA !showA; }); }, child: const Text(切换位置复用Element), ), const SizedBox(height: 20), if (showA) Child(key: globalKey) else Container( color: Colors.grey[200], child: Child(key: globalKey), ), ], ), ); } } class Child extends StatefulWidget { const Child({super.key}); override StateChild createState() _ChildState(); } class _ChildState extends StateChild { override void initState() { super.initState(); debugPrint(✅ initState); } override void deactivate() { super.deactivate(); debugPrint(⚠️ deactivateElement脱离树加入_inactiveElements); } override void activate() { super.activate(); debugPrint( activateElement重新激活复用旧Element不执行initState); } override void dispose() { super.dispose(); debugPrint(❌ dispose); } override Widget build(BuildContext context) { return const Text(GlobalKey复用我); } }点击切换只打印一次 initState 每次切换输出flutter: ⚠️ deactivateElement脱离树加入_inactiveElementsflutter: activateElement重新激活复用旧Element不执行initState这就是 GlobalKey 的魔力Element 只是被 deactivate没有 dispose重新挂载调用 activateState 完全保留。总结下来initState仅 mount 阶段执行一次update 复用 Element 不会调用。didUpdateWidgetElement 复用 (update)新旧 widget 属性变化触发mount 不会调用。deactivateElement 从树剥离不一定销毁GlobalKey 场景可以被复用。activatedeactivate 之后重新挂载 Element 时触发GlobalKey 场景特有。disposeElement 彻底 unmount 销毁deactivate 之后确认不再复用才会走到 dispose。2.自定义Element1.自定义ElementFlutter的Element、ComponentElement是可以继承重写的我们自定义MyTestElement直接重写原生生命周期mount / update / deactivate / activate / unmount。 ⚠️注意release 模式也可以跑不是仅debug。1. 概述我们以下面的代码为例import package:flutter/material.dart; import package:flutter/widgets.dart; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); override Widget build(BuildContext context) { return MaterialApp(home: Home()); } } class Home extends StatefulWidget { override StateHome createState() _HomeState(); } class _HomeState extends StateHome { bool show true; int count 0; override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text(自定义Element Demo)), body: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () { setState(() { show !show; }); }, child: const Text(添加/移除组件), ), ElevatedButton( onPressed: () { setState(() { count; }); }, child: const Text(父setState更新), ), const SizedBox(height: 20), if (show) MyTestWidget(count: count, key: const ValueKey(my_fixed_key)), ], ), ); } } // 自定义Widget重写 createElement 返回我们自己的Element class MyTestWidget extends Widget { final int count; const MyTestWidget({super.key, required this.count}); override MyTestElement createElement() { return MyTestElement(this); } } // 自定义 ComponentElement重写全部原生生命周期 class MyTestElement extends ComponentElement { MyTestElement(MyTestWidget widget) : super(widget); override MyTestWidget get widget super.widget as MyTestWidget; /// 原生Element生命周期钩子 override void mount(Element? parent, Object? newSlot) { debugPrint( [Element] mount() 挂载parent:$parent slot:$newSlot); super.mount(parent, newSlot); } override void update(covariant MyTestWidget newWidget) { debugPrint( [Element] update() 复用Element旧count:${widget.count} → 新count:${newWidget.count}); super.update(newWidget); } override void deactivate() { debugPrint( [Element] deactivate() 脱离Element树进入_inactiveElements缓存); super.deactivate(); } override void activate() { debugPrint( [Element] activate() 重新激活GlobalKey复用); super.activate(); } override void unmount() { debugPrint( [Element] unmount() 彻底销毁之后dispose); super.unmount(); } override Widget build() { debugPrint(️ [Element] build() count${widget.count}); return Text(MyTestWidget count${widget.count}); } }2.控制台输出观察页面初次打开 [Element] mount() 挂载 ️ [Element] build() count0点击父setStatekey不变canUpdatetrue复用Element [Element] update() 复用Element旧count:0 → 新count:1 ️ [Element] build() count1点击移除组件 [Element] deactivate() 脱离Element树进入_inactiveElements缓存 [Element] unmount() 彻底销毁之后dispose✨重点mount只新建节点执行一次updateElement复用时执行不会走mountdeactivate只是脱离树不一定销毁activateGlobalKey把缓存的Element重新挂载才触发unmount真正销毁Element之后无法复用。2. 如何使用GlobalKey获取 Element2.1 GlobalKey获取Element// GlobalKey 泛型可以是State也可以直接 Element final GlobalKey elementKey GlobalKey(); // build中绑定key MyTestWidget(key: elementKey)// 获取 Element Element? elem elementKey.currentElement; if(elem ! null){ debugPrint(拿到Element$elem); }2.2 GlobalKey 获取 Statefinal GlobalKey_LifeDemoChildState stateKey GlobalKey(); // build LifeDemoChild(key: stateKey) // 获取State _LifeDemoChildState? state stateKey.currentState;2.3 GlobalKey 获取 RenderObjectRenderObject? renderObj elementKey.currentContext?.findRenderObject();注意currentElement在组件mount之后才不为nullbuild阶段拿不到组件被deactivate之后currentElement会变成null但是Element还存在于_inactiveElements内部缓存GlobalKey 底层原理key作为hash把Element存入_inactiveElementsmapinflateWidget时优先从缓存取出复用。小Demo片段ElevatedButton(onPressed: (){ Element? e elementKey.currentElement; debugPrint(currentElement $e); RenderObject? ro elementKey.currentContext?.findRenderObject(); debugPrint(RenderObject $ro); }, child: const Text(打印key信息))3. 验证Element生命周期的其它方式1.Flutter开发工具debug观测DevTools → Inspector开启“Highlight rebuilds” 可以直观看到哪些Element执行rebuild。在flutter: developer包debugPrintElementCreate打开Element创建日志。2.重写Widget.canUpdate打日志封装一个代理Widget包裹任意子Widget拦截canUpdate静态方法打印是否复用。canUpdate是静态方法不能重写可以封装一层代理Widget在updateChild调用前后打印日志。3.InheritedWidgetdebugFillProperties在Inherited的updateShouldNotify打印间接观察依赖该Inherited的Element触发didChangeDependencies。4.自定义BuildOwner高级自己接管BuildOwner监听onBuildScheduled监控脏Element标记观察Element被标记dirty。// WidgetsFlutterBinding.instance.buildOwner.onBuildScheduled (){ ... }5.AOP拦截不推荐生产使用编译时AOP库例如 aspectdhook Element的mount/update/deactivate仅用于学习调试业务代码禁止。✅业务开发中优先使用State回调学习源码可以自定义Element子类。4. Flutter如何复用RenderObjectRenderObject 归属于RenderObjectElementRenderObject不能脱离Element单独复用必须依托Element复用机制。4.1 正常复用RenderObject完整条件面试必背父Element执行updateChildnewWidget ! null旧child Element存在Widget.canUpdate(oldWidget, newWidget)返回 trueruntimeType相同 key相同此时不会执行deactivateChild旧RenderObjectElement被复用调用RenderObjectElement.update(newWidget)→ 内部调用widget.updateRenderObject(element, oldWidget)更新RenderObject内部属性RenderObject实例对象不新建直接复用。✅ 关键点RenderObject复用 Element复用只要Element销毁(deactivateunmount)RenderObject就会detach并被GC回收无法保留。4.2 GlobalKey场景复用RenderObjectGlobalKey把Element放到_inactiveElements缓存Element只是deactivate没有unmount 当再次挂载走activate()RenderObject也被attach恢复RenderObject对象不变完成复用。这是唯一可以把组件从树上移走再回来还保留RenderObject/State的方式。4.3 ❌不能做的事情不能自己手动保存RenderObject变量手动attach到渲染树框架不允许会破坏Element树与RenderObject树一致性。如果canUpdatefalseElement销毁RenderObject detach彻底无法复用。4.4 Demo演示复用RenderObjectimport package:flutter/material.dart; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); override Widget build(BuildContext context) MaterialApp(home: RenderReuseDemo()); } class RenderReuseDemo extends StatefulWidget { override StateRenderReuseDemo createState() _RenderReuseDemoState(); } class _RenderReuseDemoState extends StateRenderReuseDemo { final GlobalKey keyA GlobalKey(); int count 0; override Widget build(BuildContext context) { return Scaffold( body: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton(onPressed: (){ setState(() { count; }); // 打印RenderObject内存地址 RenderObject? ro keyA.currentContext?.findRenderObject(); debugPrint(RenderObject 实例地址 $ro); }, child: const Text(setState打印RenderObject引用)), // key固定不变canUpdatetrueRenderObject复用 Container(key: keyA, child: Text(count $count)) ], ), ); } }多次点击按钮RenderObject内存地址不变证明是同一个对象没有新建。 如果修改代码每次给新的ValueKey(count)每次地址变化代表重建RenderObject。