InheritedWidget
1. 什么是 InheritedWidget?
简单来说,InheritedWidget 是一个特殊的小部件,它的主要功能是沿着 widget 树向下高效地传递数据(状态)。
你可以把它想象成:
一个全局的信息公告牌:任何在它子树下的组件都可以来“看”这个公告牌上的信息。
一个家族族谱:子孙后代都可以根据族谱来确认自己的祖先是谁。
React/Vue 中的 Context:如果你有前端开发经验,它的作用和 Context API 几乎一模一样。
它的核心目的是解决“逐层传递数据” 的麻烦。想象一下,如果最顶层的组件有一个数据(比如用户登录信息、主题配色),而深埋在第十层的一个小部件需要这个数据。如果没有 InheritedWidget,你就必须通过构造函数一层一层地把这个数据传递下去,非常繁琐且难以维护。
InheritedWidget 优雅地解决了这个问题。
2. 它是如何工作的?
InheritedWidget
本身不持有复杂的逻辑。它通常包裹在你的应用或某个模块的顶层。
提供数据:你创建一个继承自
InheritedWidget
的类(例如MyDataInheritedWidget
),它内部包含了你想要共享的数据(例如themeColor
)。查找数据:在子树下的任何一个小部件中,你都可以使用一个特定的方法(如
context.dependOnInheritedWidgetOfExactType<MyDataInheritedWidget>()
)来向上查找最近的MyDataInheritedWidget
实例,并从中获取到共享的数据。建立依赖关系:当你在一个小部件中通过
context
“查找”并“依赖”了一个InheritedWidget
后,Flutter 框架就在这个小部件和这个InheritedWidget
之间建立了一个依赖关系。
3. didChangeDependencies
和它的关系
didChangeDependencies
是 State
对象的一个生命周期方法。
调用时机:当 State 对象所依赖的
InheritedWidget
发生变化时(例如,InheritedWidget
里的数据变了,它重新构建了),Flutter 框架会自动调用所有依赖了它的组件的didChangeDependencies
方法。为什么需要它:这是一种通知机制。它告诉子组件:“嘿,你们所依赖的那个上游数据源更新了,你们可以在这里根据新数据做一些操作或者强制刷新自己了”。
工作流程串联起来就是:
一个
InheritedWidget
(例如ThemeDataInheritedWidget
)内部的主题颜色从“蓝色”变成了“红色”,这导致它重新构建。Flutter 框架检测到这个变化,然后去查看有哪些组件的
State
依赖了这个ThemeDataInheritedWidget
。框架遍历这些依赖项,并一一调用它们的
didChangeDependencies
方法。在这些组件的
didChangeDependencies
方法里,你可以选择调用setState(() {})
来触发自身重建。重建时,它们再次通过context
去获取InheritedWidget
的数据,拿到的就是最新的“红色”了。
4. 一个简单的代码示例
// 1. 创建一个继承自 InheritedWidget 的类,用于共享主题颜色
class ThemeColorInheritedWidget extends InheritedWidget {final Color themeColor; // 要共享的数据const ThemeColorInheritedWidget({super.key,required this.themeColor,required super.child,});// 这个方法是判断何时通知依赖者更新的关键@overridebool updateShouldNotify(ThemeColorInheritedWidget oldWidget) {// 如果新的 themeColor 和旧的不一样,就通知所有依赖者return themeColor != oldWidget.themeColor;}// 一个便捷方法,方便子 widget 获取这个 InheritedWidget 实例static ThemeColorInheritedWidget? of(BuildContext context) {return context.dependOnInheritedWidgetOfExactType<ThemeColorInheritedWidget>();}
}// 2. 一个使用共享数据的子 Widget
class MyTextWidget extends StatefulWidget {const MyTextWidget({super.key});@overrideState<MyTextWidget> createState() => _MyTextWidgetState();
}class _MyTextWidgetState extends State<MyTextWidget> {Color? _currentColor;@overridevoid didChangeDependencies() {super.didChangeDependencies();// 当所依赖的 ThemeColorInheritedWidget 改变时,这个方法会被调用// 我们在这里获取最新的颜色,并更新自身状态final newColor = ThemeColorInheritedWidget.of(context)?.themeColor;if (newColor != null && newColor != _currentColor) {setState(() {_currentColor = newColor;});}}@overrideWidget build(BuildContext context) {// 初始化和构建时获取颜色_currentColor ??= ThemeColorInheritedWidget.of(context)?.themeColor;return Text('Hello World',style: TextStyle(color: _currentColor ?? Colors.black),);}
}// 3. 在应用顶层使用它
void main() {runApp(const MyApp());
}class MyApp extends StatefulWidget {const MyApp({super.key});@overrideState<MyApp> createState() => _MyAppState();
}class _MyAppState extends State<MyApp> {Color _currentThemeColor = Colors.blue;void _changeTheme() {setState(() {// 切换主题颜色,这会使得 ThemeColorInheritedWidget 重建_currentThemeColor =_currentThemeColor == Colors.blue ? Colors.red : Colors.blue;});}@overrideWidget build(BuildContext context) {return MaterialApp(home: Scaffold(appBar: AppBar(title: const Text('InheritedWidget Example'),),body: ThemeColorInheritedWidget(// 将颜色数据提供给子树themeColor: _currentThemeColor,child: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [const MyTextWidget(), // 这个 widget 会依赖上面的主题色ElevatedButton(onPressed: _changeTheme,child: const Text('Change Theme Color'),),],),),),),);}
}
在这个例子中:
点击按钮,调用
_changeTheme
,改变_currentThemeColor
。这导致顶层的
ThemeColorInheritedWidget
重新构建。它的
updateShouldNotify
方法返回true
(因为颜色确实变了)。Flutter 通知依赖了它的
_MyTextWidgetState
,调用其didChangeDependencies
方法。在
didChangeDependencies
中,我们获取新的颜色并通过setState
更新自己,文本颜色随之改变。
5. 怎样理解didChangeDependencies
只是一个通知机制?
Q:如果_MyTextWidgetState 里面的didChangeDependencies方法里没有调用setState,那_MyTextWidgetState 的build会执行吗?
A:不会。
如果 didChangeDependencies
方法里没有调用 setState
,那么 _MyTextWidgetState
的 build
方法不会因为 InheritedWidget
的变更而被再次执行。
详细解释:
让我们来分解一下整个流程,理解为什么不会:
触发变更:
ThemeColorInheritedWidget
的数据(themeColor
)改变了,导致它重新构建。通知依赖项:在重建时,Flutter 会调用
ThemeColorInheritedWidget
的updateShouldNotify
方法。如果返回true
,Flutter 框架就知道需要通知所有依赖于此InheritedWidget
的State
对象。调用
didChangeDependencies
:框架遍历所有依赖项,并调用它们的didChangeDependencies
方法。这一步只是一个“通知”,它本身并不会引起组件重建。重建的关键 -
setState
:didChangeDependencies
只是一个回调方法,一个让你有机会对依赖变更做出反应的钩子。你是否要做出反应(即重新构建 UI)完全取决于你。你通过调用setState
来明确告诉 Flutter:“我收到了通知,并且我的状态需要更新,请重新构建我的 UI”。如果你不调用setState
,Flutter 就认为这个 State 虽然依赖变了,但不需要更新 UI,因此不会调用build
方法。
类比
可以把这想象成一个新闻订阅:
InheritedWidget
:是一家报社。didChangeDependencies
:是报社将新一期的报纸(数据变了)投递到你家的邮箱里。你知道有新报纸了(通知已送达)。setState
:是你决定走去邮箱拿出报纸并阅读它(读取新数据)的这个动作。这个动作会导致你的知识状态更新(UI 状态更新)。build
方法:是根据你刚读到的新闻内容,向别人复述(重建 UI)。
如果你只是知道报纸送到了(didChangeDependencies
被调用),但懒得去拿(不调用 setState
),那么你就不会获得新信息,你向别人复述的内容(build
产生的 UI)自然也就还是旧的。
划重点:
didChangeDependencies
只是一个通知机制。setState
是请求重建的机制。build
是执行重建的过程。
通知 (didChangeDependencies
) 并不会自动导致重建 (build
),你必须手动将两者通过 setState
连接起来。 这种设计给了开发者更大的灵活性,你可以选择在某些依赖变更时忽略UI更新,或者只执行一些其他逻辑(如日志记录、网络请求等)。
总结
概念 | 角色 |
---|---|
InheritedWidget | 数据的提供者。像一个广播塔,负责持有数据和通知变更。 |
didChangeDependencies | 消费者的响应器。当广播塔(InheritedWidget)的信号变了,依赖它的消费者就会收到这个回调,从而做出反应。 |
依赖关系 | 通过 context.dependOnInheritedWidgetOfExactType 建立的联系,框架借此知道该通知谁。 |
虽然在实际开发中,我们更多地使用 Provider
、Bloc
等基于 InheritedWidget
封装的先进状态管理库,但理解其底层原理 InheritedWidget
和 didChangeDependencies
的工作机制对于深入掌握 Flutter 至关重要。