ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

深入理解Flutter:StatefulWidget生命周期全解析(二)

深入理解Flutter:StatefulWidget生命周期全解析(二) 概述上一篇博客介绍了StatefulWidget的生命周期函数这篇博客介绍下生命周期中的两个函数的调用时机。1.didChangeDependencies1. 概述didChangeDependencies是 FlutterState类中的一个生命周期方法。当State对象所依赖的InheritedWidget发生变化时框架会调用此方法。2. 核心调用时机1.首次挂载在initState方法执行完毕后框架会立即调用didChangeDependencies。2.依赖的 InheritedWidget 发生变化当此State所依赖的InheritedWidget发生更新例如其数据发生变化时框架会调用此方法。典型的触发场景1.系统切换深色/浅色主题Theme 变2.旋转屏幕 / 分屏MediaQuery 变3.自定义InheritedWidget如自写UserInfo.of(context)数据更新4.Provider/Riverpod 底层也是这套机制Provider 本质是 InheritedWidget 封装2.didUpdateWidget函数的调用时机1. didUpdateWidget概述一句话父 Widget 用同一个 StatefulElement 但换了新 Widget 实例时调用——也就是「配置更新State 复用」的那一刻。我们从框架源码这段可以看出来// framework.dart → StatefulElement.update() override void update(StatefulWidget newWidget) { final oldWidget state._widget; state._widget newWidget; // ① 先换新引用 state.didUpdateWidget(oldWidget); // ② 再通知 State super.update(newWidget); // ③ 里面标脏 → build() }所以调用顺序是didUpdateWidget(old) → build()initState 不会在这里调State 对象完全没变变的只是它持有的 widget 引用。2. 最常见的4中触发场景1.父组件传了新的属性(props)我们以下面的demo为例图1.修改父组件的属性在上述的实例中我们把点击切换按钮的时候切换子组件的颜色。void _switchColor() { //生成子组件的颜色 setState(() { _colorIndex (_colorIndex 1) % _colors.length; debugPrint([setState] 修改父组件属性 _colorIndex → 颜色 ${_colors[_colorIndex]}); }); } // 子组件代码 使用 color创建子组件 class _UpdateChild extends StatefulWidget { /// 当前颜色父组件属性变化时触发本组件 didUpdateWidget final Color color; /// 点击子组件按钮时回调父组件由父组件修改自己的属性 final VoidCallback onClick; /// 子组件生命周期日志上报给父组件统一展示 final void Function(String method, String message) onLog; const _UpdateChild({ required this.color, required this.onClick, required this.onLog, }); override // ignore: no_logic_in_create_state - 本页面用于演示生命周期调用顺序日志为有意为之 State_UpdateChild createState() { debugPrint([子类 didChangeDependencies] 方法被调用); return _UpdateChildState(); } }同时我们给子组件的各个生命周期的方法增加打印日志首次进入页面的时候控制台打印日志如下flutter: [子类 didChangeDependencies] 方法被调用flutter: [子类 initState] 方法被调用flutter: [子类 didChangeDependencies] 方法被调用flutter: [子类 build] 方法被调用当我们点击修改按钮之后控制台打印日志如下flutter: [子类 didUpdateWidget] 方法被调用flutter: [子类 build] 方法被调用完整的代码如下import package:flutter/material.dart; class DidUpdateWidgetDemoPage extends StatefulWidget { final String title; const DidUpdateWidgetDemoPage({super.key, required this.title}); override // ignore: no_logic_in_create_state - 本页面用于演示生命周期调用顺序日志为有意为之 StateDidUpdateWidgetDemoPage createState(){ return _DidUpdateWidgetDemoPageState(); } } class _DidUpdateWidgetDemoPageState extends StateDidUpdateWidgetDemoPage { static const ListColor _colors [Colors.blue, Colors.orange, Colors.green]; /// 子组件配置参数颜色 int _colorIndex 0; void _switchColor() { setState(() { _colorIndex (_colorIndex 1) % _colors.length; debugPrint([setState] 修改父组件属性 _colorIndex → 颜色 ${_colors[_colorIndex]}); }); } override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(widget.title), ), body: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: 12), Center( child: _buildControls(context), ), const SizedBox(height: 8), _UpdateChild( color: _colors[_colorIndex], onClick: _switchColor, ) ], ), ); } /// 操作区 Widget _buildControls(BuildContext context) { return Wrap( spacing: 8, runSpacing: 8, crossAxisAlignment: WrapCrossAlignment.center, children: [ OutlinedButton.icon( onPressed: _switchColor, icon: const Icon(Icons.palette), label: const Text(切换子组件颜色), ), ], ); } } /// 子组件父组件重建时由框架调用 didUpdateWidgetState 复用。 /// /// 通过回调与父组件通信点击子组件按钮 → 调用父组件传入的 onSwitch 回调 /// → 父组件修改自己的属性并重建 → 子组件收到新配置 → 触发 didUpdateWidget。 class _UpdateChild extends StatefulWidget { /// 当前颜色父组件属性变化时触发本组件 didUpdateWidget final Color color; /// 点击子组件按钮时回调父组件由父组件修改自己的属性 final VoidCallback onClick; const _UpdateChild({ required this.color, required this.onClick, }); override // ignore: no_logic_in_create_state - 本页面用于演示生命周期调用顺序日志为有意为之 State_UpdateChild createState() { debugPrint([子类 didChangeDependencies] 方法被调用); return _UpdateChildState(); } } class _UpdateChildState extends State_UpdateChild { override void initState() { super.initState(); debugPrint([子类 initState] 方法被调用); } override void didChangeDependencies() { super.didChangeDependencies(); debugPrint([子类 didChangeDependencies] 方法被调用); } override void didUpdateWidget(covariant _UpdateChild oldWidget) { super.didUpdateWidget(oldWidget); debugPrint([子类 didUpdateWidget] 方法被调用); } override void deactivate() { super.deactivate(); debugPrint([子类 deactivate] 方法被调用); } override void dispose() { super.dispose(); debugPrint([子类 dispose] 方法被调用); } override Widget build(BuildContext context) { debugPrint([子类 build] 方法被调用); final color widget.color; return Card( elevation: 0, color: color.withValues(alpha: 0.08), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: BorderSide(color: color.withValues(alpha: 0.4), width: 2), ), child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ Icon(Icons.swap_horiz, color: color, size: 36), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(子组件 _UpdateChild, style: Theme.of(context).textTheme.titleSmall), const SizedBox(height: 2), Text( 当前颜色${_colorName(color)}, style: Theme.of(context) .textTheme .bodySmall ?.copyWith(color: color, fontWeight: FontWeight.w600), ), const SizedBox(height: 2), Text( 点击「子组件换色」→ 回调父组件 → 父修改属性 → 触发 didUpdateWidget, style: Theme.of(context).textTheme.bodySmall, ), ], ), ), ], ), ), ); } String _colorName(Color c) { if (c Colors.blue) return 蓝; if (c Colors.orange) return 橙; if (c Colors.green) return 绿; return 未知; } }2.父类Widget重建但是Key相同以下面的demo为例。图2.父类重建但是Key相同在上述的代码中我们点击切换按钮的时候都会调用build的方法创建一个新的Widget实例但是Key相同子类的didIUpdateWidget会被调用。控制台打印日志如下flutter: [子类 didUpdateWidget] 方法被调用flutter: [子类 build] 方法被调用完整代码如下import package:flutter/material.dart; class DidSameKeyWidgetDemoPage extends StatefulWidget { final String title; const DidSameKeyWidgetDemoPage({super.key, required this.title}); override // ignore: no_logic_in_create_state - 本页面用于演示生命周期调用顺序日志为有意为之 StateDidSameKeyWidgetDemoPage createState(){ return _DidSameKeyWidgetDemoPageState(); } } class _DidSameKeyWidgetDemoPageState extends StateDidSameKeyWidgetDemoPage { static const ListColor _colors [Colors.blue, Colors.orange, Colors.green]; int _colorIndex 0; override void initState() { super.initState(); debugPrint([子类 initState] 方法被调用); } override void didChangeDependencies() { super.didChangeDependencies(); debugPrint([子类 didChangeDependencies] 方法被调用); } void _switchColor() { setState(() { _colorIndex (_colorIndex 1) % _colors.length; debugPrint([setState] 修改父组件属性 _colorIndex → 颜色 ${_colors[_colorIndex]}); }); } override Widget build(BuildContext context) { debugPrint([父类 build] 方法被调用); return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(widget.title), ), body: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: 20), Center( child: _buildControls(context), ), const SizedBox(height: 8), _UpdateChild( key:const ValueKey(child), ) ], ), ); } /// 操作区 Widget _buildControls(BuildContext context) { return Wrap( spacing: 8, runSpacing: 8, crossAxisAlignment: WrapCrossAlignment.center, children: [ OutlinedButton.icon( onPressed: _switchColor, icon: const Icon(Icons.palette), label: const Text(切换父组件颜色), style: OutlinedButton.styleFrom( backgroundColor: _colors[_colorIndex], ), ), ], ); } } class _UpdateChild extends StatefulWidget { const _UpdateChild({super.key}); override // ignore: no_logic_in_create_state - 本页面用于演示生命周期调用顺序日志为有意为之 State_UpdateChild createState() { debugPrint([子类 didChangeDependencies] 方法被调用); return _UpdateChildState(); } } class _UpdateChildState extends State_UpdateChild { override void initState() { super.initState(); debugPrint([子类 initState] 方法被调用); } override void didChangeDependencies() { super.didChangeDependencies(); debugPrint([子类 didChangeDependencies] 方法被调用); } override void didUpdateWidget(covariant _UpdateChild oldWidget) { super.didUpdateWidget(oldWidget); debugPrint([子类 didUpdateWidget] 方法被调用); } override void deactivate() { super.deactivate(); debugPrint([子类 deactivate] 方法被调用); } override void dispose() { super.dispose(); debugPrint([子类 dispose] 方法被调用); } override Widget build(BuildContext context) { debugPrint([子类 build] 方法被调用); return Card( elevation: 0, color: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: BorderSide(color: Colors.blue, width: 2), ), child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ Icon(Icons.swap_horiz, color: Colors.blue, size: 36), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(子组件 , style: Theme.of(context).textTheme.titleSmall), const SizedBox(height: 2), Text( 当前颜色蓝色, style: Theme.of(context) .textTheme .bodySmall ), const SizedBox(height: 2), Text( 点击「子组件换色」→ 回调父组件 → 父修改属性 → 触发 didUpdateWidget, style: Theme.of(context).textTheme.bodySmall, ), ], ), ), ], ), ), ); } }3.热重载Hot Reload热重载时框架会尽量保留 State 树但 Widget 树重新创建 → 新 Widget 实例 →didUpdateWidget被调。4.祖先 InheritedWidget 变化严格说InheritedWidget变化直接触发的是didChangeDependencies但如果父组件因此重建并传了新参那didUpdateWidget也会跟着来。两者可以同时触发。完整的demo如下import package:flutter/material.dart; void main() runApp(const Root()); /// ─── 1. InheritedWidget ─────────────────────────────── class CounterData extends InheritedWidget { final int value; const CounterData({required this.value, required super.child}); static CounterData? of(BuildContext context) { return context.dependOnInheritedWidgetOfExactTypeCounterData(); } override bool updateShouldNotify(CounterData old) old.value ! value; } /// ─── 2. 根持有全局计数包一层 InheritedWidget ─────── class Root extends StatefulWidget { const Root({super.key}); override StateRoot createState() _RootState(); } class _RootState extends StateRoot { int _global 0; override Widget build(BuildContext context) { print( Root build() — _global$_global); return CounterData( value: _global, child: MaterialApp( home: Scaffold( appBar: AppBar(title: const Text(InheritedWidget didUpdateWidget)), body: const Parent(), floatingActionButton: FloatingActionButton( onPressed: () setState(() _global), child: const Icon(Icons.add), ), ), ), ); } } /// ─── 3. 父 Widget从 InheritedWidget 读数据传给子 ─── class Parent extends StatefulWidget { const Parent({super.key}); override StateParent createState() _ParentState(); } class _ParentState extends StateParent { override Widget build(BuildContext context) { final count CounterData.of(context)!.value; print( Parent build() — 从 InheritedWidget 读到 count$count); return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ ChildWidget( key: const ValueKey(child), count: count, // ← 每次 _global 变这里传新值 ), ], ), ); } } /// ─── 4. 子 StatefulWidget同时观察两个回调 ────────── class ChildWidget extends StatefulWidget { final int count; const ChildWidget({required this.count, super.key}); override StateChildWidget createState() _ChildWidgetState(); } class _ChildWidgetState extends StateChildWidget { override void initState() { super.initState(); print( Child initState()); } override void didChangeDependencies() { super.didChangeDependencies(); final v CounterData.of(context)!.value; print( Child didChangeDependencies() — InheritedWidget value$v); } override void didUpdateWidget(covariant ChildWidget old) { super.didUpdateWidget(old); print( Child didUpdateWidget() — old.count${old.count}, new.count${widget.count}); } override Widget build(BuildContext context) { print( ⚪ Child build() — count${widget.count}); return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( border: Border.all(color: Colors.deepPurple, width: 2), borderRadius: BorderRadius.circular(8), ), child: Text(Child count: ${widget.count}, style: const TextStyle(fontSize: 18)), ); } override void dispose() { print( Child dispose()); super.dispose(); } }在上述的代码中① 用户点 FAB② Root.setState → Root.build() 重新执行③ CounterData(value: 1) 是【新实例】→ InheritedElement.update() 发现 updateShouldNotifytrue→ 通知所有依赖者Parent Child调用 didChangeDependencies()④ Parent.build() 重新执行→ CounterData.of(context) 读到 value1→ 创建【新的】ChildWidget(count: 1) 实例⑤ ChildWidget 的 Element 发现 widget 换了key 相同Element 复用→ didUpdateWidget(oldWidget) 被调用⑥ Element 被标脏 → build() 执行
返回列表