InheritedWidget
是 Flutter 中非常重要的一个功能型组件,它提供了一种在 widget 树中从上到下共享数据的方式,比如我们在应用的根 widget 中通过InheritedWidget
共享了一个数据,那么我们便可以在任意子widget 中来获取该共享的数据!这个特性在一些需要在整个 widget 树中共享数据的场景中非常方便!如Flutter SDK中正是通过 InheritedWidget 来共享应用主题(Theme
)和 Locale (当前语言环境)信息的。
但是对于初学者(包括博主自己)可能因为对组件的生命周期不是很熟悉会导致函数的调用实际会有点混乱,所有在参考的文章基础上添加了对flutter组件生命周期的讲解来帮助大家更好的理解数据的流转过程
Flutter 组件的生命周期回顾
在深入 InheritedWidget
之前,我们先回顾一下 Flutter 组件的生命周期:
class MyWidget extends StatefulWidget {
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
void initState() {
super.initState();
print("1. initState: 组件初始化");
}
void didChangeDependencies() {
super.didChangeDependencies();
print("2. didChangeDependencies: 依赖发生变化");
}
Widget build(BuildContext context) {
print("3. build: 构建界面");
return Container();
}
void didUpdateWidget(MyWidget oldWidget) {
super.didUpdateWidget(oldWidget);
print("4. didUpdateWidget: 组件更新");
}
void dispose() {
print("5. dispose: 组件销毁");
super.dispose();
}
}
执行顺序:
initState()
→ 组件创建时调用一次didChangeDependencies()
→ 依赖变化时调用build()
→ 构建/重建界面时调用didUpdateWidget()
→ 父组件重建导致子组件更新时调用dispose()
→ 组件销毁时调用
InheritedWidget 的生命周期详解
现在让我们看看 InheritedWidget
是如何融入这个生命周期的:
1. updateShouldNotify:数据变化的"判断者"
class ShareDataWidget extends InheritedWidget {
final int data;
ShareDataWidget({required this.data, required Widget child})
: super(child: child);
bool updateShouldNotify(ShareDataWidget oldWidget) {
print("updateShouldNotify被调用:旧值=${oldWidget.data}, 新值=$data");
// 只有当数据真正变化时才返回true
return oldWidget.data != data;
}
}
何时调用:当父组件调用 setState()
导致 InheritedWidget
重建时
调用时机:在 build()
方法执行完成后,Flutter 框架会比较新旧 InheritedWidget
作用:决定是否需要通知依赖的子组件更新
2. dependOnInheritedWidgetOfExactType:建立依赖关系
static ShareDataWidget? of(BuildContext context) {
print("正在查找并建立依赖关系...");
return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
}
何时调用:在子组件的 build()
方法中调用 ShareDataWidget.of(context)
时
作用:
- 查找最近的
ShareDataWidget
- 建立依赖关系(这是关键的一步,后文会详细说明)
- 返回找到的
InheritedWidget
实例
3. didChangeDependencies:依赖变化的"通知员"
class _TestWidget extends StatefulWidget {
__TestWidgetState createState() => __TestWidgetState();
}
class __TestWidgetState extends State<_TestWidget> {
void didChangeDependencies() {
super.didChangeDependencies();
print("didChangeDependencies被调用:依赖的数据发生了变化");
}
Widget build(BuildContext context) {
print("build被调用:正在重建界面");
return Text(ShareDataWidget.of(context)!.data.toString());
}
}
何时调用:
- 组件首次创建后(在
initState()
之后) - 依赖的
InheritedWidget
数据变化且updateShouldNotify
返回true
时
完整的执行流程演示
让我们通过一个完整的例子来看看整个流程:
class InheritedWidgetDemo extends StatefulWidget {
_InheritedWidgetDemoState createState() => _InheritedWidgetDemoState();
}
class _InheritedWidgetDemoState extends State<InheritedWidgetDemo> {
int count = 0;
Widget build(BuildContext context) {
print("=== 父组件 build 开始,count=$count ===");
return Scaffold(
appBar: AppBar(title: Text('InheritedWidget 生命周期演示')),
body: ShareDataWidget(
data: count,
child: Column(
children: [
TestChildWidget(),
ElevatedButton(
onPressed: () {
print("\n🔄 点击按钮,即将调用 setState");
setState(() {
count++;
});
},
child: Text('点击增加: $count'),
),
],
),
),
);
}
}
class ShareDataWidget extends InheritedWidget {
final int data;
ShareDataWidget({
Key? key,
required this.data,
required Widget child,
}) : super(key: key, child: child);
static ShareDataWidget? of(BuildContext context) {
print("📡 调用 dependOnInheritedWidgetOfExactType,建立依赖关系");
return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
}
bool updateShouldNotify(ShareDataWidget oldWidget) {
bool shouldNotify = oldWidget.data != data;
print("⚖️ updateShouldNotify: 旧值=${oldWidget.data}, 新值=$data, 是否通知=$shouldNotify");
return shouldNotify;
}
}
class TestChildWidget extends StatefulWidget {
_TestChildWidgetState createState() => _TestChildWidgetState();
}
class _TestChildWidgetState extends State<TestChildWidget> {
void initState() {
super.initState();
print("🏗️ 子组件 initState");
}
void didChangeDependencies() {
super.didChangeDependencies();
print("📢 子组件 didChangeDependencies:依赖发生变化!");
}
Widget build(BuildContext context) {
print("🎨 子组件 build 开始");
// 在这里建立依赖关系
final sharedData = ShareDataWidget.of(context)!;
print("🎨 子组件 build 完成,显示数据: ${sharedData.data}");
return Container(
padding: EdgeInsets.all(20),
margin: EdgeInsets.all(20),
decoration: BoxDecoration(
border: Border.all(color: Colors.blue),
borderRadius: BorderRadius.circular(8),
),
child: Text(
'共享数据: ${sharedData.data}',
style: TextStyle(fontSize: 24),
),
);
}
}
执行流程分析
首次启动时的执行顺序:
=== 父组件 build 开始,count=0 ===
🏗️ 子组件 initState
📢 子组件 didChangeDependencies:依赖发生变化!
🎨 子组件 build 开始
📡 调用 dependOnInheritedWidgetOfExactType,建立依赖关系
🎨 子组件 build 完成,显示数据: 0
解释:
- 父组件
build()
创建ShareDataWidget
和子组件 - 子组件初始化:
initState()
→didChangeDependencies()
→build()
- 在子组件的
build()
中调用ShareDataWidget.of(context)
建立依赖关系
点击按钮后的执行顺序:
🔄 点击按钮,即将调用 setState
=== 父组件 build 开始,count=1 ===
⚖️ updateShouldNotify: 旧值=0, 新值=1, 是否通知=true
📢 子组件 didChangeDependencies:依赖发生变化!
🎨 子组件 build 开始
📡 调用 dependOnInheritedWidgetOfExactType,建立依赖关系
🎨 子组件 build 完成,显示数据: 1
解释:
setState()
触发父组件重建- 新的
ShareDataWidget
创建完成后,Flutter 调用updateShouldNotify()
- 因为返回
true
,所以通知依赖的子组件 - 子组件的
didChangeDependencies()
被调用 - 子组件重新
build()
关键概念深度解析
1. 依赖关系是如何建立的?(深度解析)
context
提供了两种主要方式来获取 InheritedWidget
,它们的效果截然不同:
context.dependOnInheritedWidgetOfExactType<T>()
: 获取数据 + 建立依赖。当InheritedWidget
更新时,会触发调用此方法的子组件重建。context.getElementForInheritedWidgetOfExactType<T>().widget
: 仅获取数据,不建立依赖。子组件不会因为InheritedWidget
的更新而重建。
场景对比:
假设 ShareDataWidget
中有一个方法,而某个子组件只需要调用该方法,但不需要显示其数据。
// 1. 需要显示数据,必须监听变化
class DataDisplayWidget extends StatelessWidget {
Widget build(BuildContext context) {
// 使用 dependOn... 建立依赖
final data = ShareDataWidget.of(context)!.data;
return Text('共享数据: $data'); // 数据变化时,这里会重建
}
}
// 2. 只想调用方法,不需要监听数据变化
class ActionButton extends StatelessWidget {
Widget build(BuildContext context) {
// 使用 getElementFor... 不建立依赖
final inheritedElement = context.getElementForInheritedWidgetOfExactType<ShareDataWidget>();
// 假设 ShareDataWidget 有一个 onReset 方法
// final onReset = (inheritedElement?.widget as ShareDataWidget).onReset;
return ElevatedButton(
onPressed: () { /* onReset(); */ }, // 调用方法
child: Text('执行操作'), // InheritedWidget数据变化时,这里不会重建
);
}
}
这个技巧是性能优化的关键,也是 Provider
包中 context.watch
(监听) 和 context.read
(不监听) 的实现原理。
2. 为什么需要 updateShouldNotify?
updateShouldNotify(ShareDataWidget oldWidget) {
// 如果总是返回 true
// return true; // 导致性能开销极大!每次都会通知所有子组件
// 如果总是返回 false
// return false; // 功能失效!子组件永远不会更新
// 正确的做法:只有数据真正变化时才通知
return oldWidget.data != data; // 性能优化
}
bool
3. didChangeDependencies 的触发时机
class SmartChildWidget extends StatefulWidget {
_SmartChildWidgetState createState() => _SmartChildWidgetState();
}
class _SmartChildWidgetState extends State<SmartChildWidget> {
void didChangeDependencies() {
super.didChangeDependencies();
print("依赖变化,执行昂贵操作...");
// 在这里执行昂贵操作,而不是在 build 中
_loadExpensiveData();
}
void _loadExpensiveData() {
// 网络请求、复杂计算等
}
Widget build(BuildContext context) {
// build 方法应该保持轻量
final data = ShareDataWidget.of(context)!.data;
return Text('数据: $data');
}
}
一般来说,子 widget 很少会重写此方法,因为在依赖改变后 Flutter 框架也都会调用build()
方法重新构建组件树。但是,如果你需要在依赖改变后执行一些昂贵的操作,比如网络请求,这时最好的方式就是在此方法中执行,这样可以避免每次build()
都执行这些昂贵操作。
常见陷阱
在
initState
中调用of(context)
:- 错误:
initState
在组件生命周期中执行得非常早,此时依赖系统还没有完全准备好。在initState
中调用dependOnInheritedWidgetOfExactType
会抛出异常。 - 正确:应该在
didChangeDependencies
或build
方法中调用。
- 错误:
updateShouldNotify
的错误实现:- 性能陷阱:
return true;
会导致即使数据没有变化,所有依赖的子组件也会被不必要地重建。 - 功能错误:如果
InheritedWidget
中有多个数据字段,忘记在updateShouldNotify
中进行比较,会导致某些数据更新时 UI 不刷新。// 错误示范:只比较了 data1 // return oldWidget.data1 != data1; // 正确示范:应该比较所有相关数据 return oldWidget.data1 != data1 || oldWidget.data2 != data2;
- 性能陷阱:
BuildContext
使用错误:- 问题:如果在创建
ShareDataWidget
的同一个build
方法的context
中去查找它,会失败。因为of(context)
是向上查找,它无法找到和自己同级的 Widget。 - 解决方案:确保用于查找的
context
来自一个被ShareDataWidget
包裹的子组件。通常使用Builder
Widget 或将子组件拆分成独立的StatelessWidget
来获取正确的context
。
- 问题:如果在创建
性能优化技巧
1. 避免不必要的依赖
class OptimizedWidget extends StatelessWidget {
Widget build(BuildContext context) {
// 错误:即使不需要数据也建立了依赖
final inherited = ShareDataWidget.of(context);
return Text('固定文本'); // 没有使用 inherited.data
// 正确:不使用数据就不要建立依赖
return Text('固定文本');
}
}
2. 条件性建立依赖
class ConditionalWidget extends StatelessWidget {
final bool needsSharedData;
ConditionalWidget({required this.needsSharedData});
Widget build(BuildContext context) {
if (needsSharedData) {
// 只有需要时才建立依赖
final data = ShareDataWidget.of(context)!.data;
return Text('共享数据: $data');
}
return Text('不需要共享数据');
}
}
适用场景与现代替代方案
InheritedWidget
非常适合用于传递那些不经常变化的、全局性的数据。最典型的例子就是 Flutter 框架自身的 Theme.of(context)
和 MediaQuery.of(context)
。
虽然 InheritedWidget
功能强大,但直接使用它会带来一些模板代码(boilerplate code)。为了简化开发,社区基于 InheritedWidget
封装了更易用的状态管理库。
- Provider: 一个轻量级的依赖注入和状态管理库,它极大地简化了
InheritedWidget
的使用。你可以通过阅读这篇笔记来学习它的实现原理:Flutter Provider 模式实现:基于 InheritedWidget 的状态管理 - Riverpod: Provider 的作者开发的下一代状态管理库,它解决了 Provider 中一些固有的问题(如对
BuildContext
的依赖),提供了编译时安全,并且更灵活。
结论:理解 InheritedWidget
的工作原理至关重要,它是所有高级状态管理方案的基石。但在实际项目中,为了提高开发效率和可维护性,我们更推荐使用 Provider
或 Riverpod
。
总结
理解 InheritedWidget
的关键在于掌握这几个核心概念:
- updateShouldNotify:数据变化时的"过滤器",决定是否通知子组件
- dependOnInheritedWidgetOfExactType:建立依赖关系的"桥梁"
- didChangeDependencies:依赖变化时的"响应器"
- build:界面重建的"执行者"
它们在组件生命周期中的协作流程:
setState()
↓
父组件 build()
↓
创建新的 InheritedWidget
↓
updateShouldNotify() 判断是否需要通知
↓ (如果返回 true)
子组件 didChangeDependencies()
↓
子组件 build()
↓
界面更新完成