✅ 一句话总览
依赖关系是在「子 widget 调用 dependOnInheritedWidgetOfExactType()」时建立的。
Flutter 会把这个子 widget 注册为依赖者,并在 InheritedWidget 更新时通知它重新 build。
🔍 一步步解释
① InheritedWidget 本身不会主动通知
InheritedWidget 是 不可变对象(immutable) 。
它本身不会“主动记录”有哪些依赖者,也不会主动通知谁。
所有依赖记录都是由 Element 层 管理。
② build 时,子 widget 去“声明依赖”
例如:
final theme = MyTheme.of(context);
内部调用:
context.dependOnInheritedWidgetOfExactType<MyTheme>();
这个方法会做两件事:
✔ 1. 找到最近的 InheritedWidget
并返回它。
✔ 2. 建立依赖关系(重点)
Flutter 找到 MyThemeElement(InheritedElement 的子类)后,会执行:
element.dependOnInheritedElement(inheritedElement)
这个调用会让 当前 context 的 Element 被记录到 inheritedElement 的依赖列表里。
📌 此时依赖关系正式建立。
③ 依赖关系存在哪?
不是存在 Widget,而是存在 InheritedElement 中。
内部结构类似:
class InheritedElement extends ProxyElement {
final Map<Element, Object> _dependents = {};
}
_dependents 存的就是所有依赖这个 InheritedWidget 的子 Element。
④ 当父数据变化,如何通知子 Widget?
当父 widget 更新时,通常是通过:
setState(() {})
然后 Flutter 会重建 InheritedWidget,如果 new widget 不等于 old widget 的 shouldNotify:
bool shouldNotify(covariant MyTheme oldWidget) => true;
那么 InheritedElement 会执行:
notifyClients(oldWidget);
这个方法会遍历所有 _dependents:
- 让它们
markNeedsBuild() - 使得依赖它们的 widget 自动重建
⑤ 子 Widget 如何区分 使用依赖 or 只访问?
- dependOnInheritedWidgetOfExactType
→ 建立依赖关系(会被通知) - getElementForInheritedWidgetOfExactType
→ 不建立依赖关系(不会被通知)
这就是有些场景能防止 rebuild 的原因。
👀 关键总结(最重要的三句话)
| 步骤 | 说明 |
|---|---|
| 1 | 依赖是在 子 widget 调用 dependOnInheritedWidgetOfExactType 时建立的 |
| 2 | 依赖关系存放在 InheritedElement 的 _dependents 里 |
| 3 | 在 InheritedWidget 更新并且 shouldNotify == true 时,会通知所有 dependents 重建 |
📦 简化图示
MyTheme (InheritedWidget)
|
| (dependOnInheritedWidgetOfExactType)
↓
ChildA ←—— 被登记为依赖者
ChildB ←—— 被登记为依赖者
当 themes 改变:
MyThemeElement.notifyClients()
├→ ChildA.markNeedsBuild()
└→ ChildB.markNeedsBuild()
好的,我把 InheritedWidget 全体系的底层逻辑一次性全部讲清楚,从原理、性能、源码到和 Provider/Riverpod 的关系,做到你以后看到任何 InheritedWidget 相关框架都会秒懂。
📌 目录
- InheritedWidget 建立依赖的完整过程(源码级)
- 为什么 InheritedWidget 查找是 O(1)
- InheritedElement.notifyClients 的源码解析
- InheritedWidget vs Provider vs Riverpod(深度对比)
- 如何自定义一个高性能 InheritedWidget
- 常见误区与优化技巧
1️⃣ InheritedWidget 建立依赖的完整过程(源码级解析)
你写:
MyTheme theme = MyTheme.of(context);
内部实现一般是:
static MyTheme of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MyTheme>()!;
}
🔍 dependOnInheritedWidgetOfExactType 做了什么?
➤ (1) 从当前 Element 向上查找最近的 InheritedElement
这是通过 Element 的 _inheritedWidgets 完成的。
➤ (2) 在 InheritedElement 的 _dependents 中注册当前 Element
源码(精简):
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor) {
registerDependency(ancestor);
return ancestor.widget;
}
void registerDependency(InheritedElement ancestor) {
ancestor._dependents[this] = null;
}
📌 依赖关系是存储在 InheritedElement 的 _dependents map 中。
➤ (3) 返回 inherited widget 实例
也就是你的 MyTheme。
2️⃣ 为什么 InheritedWidget 查找是 O(1)?
传统理解:
从树往上找 widget → 应该是 O(n)
但 Flutter 不是这样!
Flutter 的做法:
每个 Element 都有一个 map:
Map<Type, InheritedElement> _inheritedWidgets
当构建树的时候,InheritedElement 会把自己写进所有子节点的 _inheritedWidgets:
root
├─ A(有 MyTheme)
│ ├─ B(自动继承了 A 的 inherited map)
│ └─ C
└─ D
🟢 所以查找的时候只需:
context._inheritedWidgets[MyTheme]
真正是 O(1) 的查找。
3️⃣ notifyClients 源码解析(InheritedWidget 的核心)
源码(简化):
void notifyClients(InheritedWidget oldWidget) {
for (Element dependent in _dependents.keys) {
dependent.didChangeDependencies();
}
}
➤ didChangeDependencies() 会触发子 widget rebuild
继续:
@override
void didChangeDependencies() {
markNeedsBuild();
}
最终:
- 所有 dependents 在下一帧自动重建
- rebuild 是精准的,只通知依赖它的 widget,不会向下广播给无关 widget
📌 InheritedWidget 是 Flutter 最精准的依赖跟踪系统,比 Vue 的响应式还精细。
4️⃣ InheritedWidget vs Provider vs Riverpod(深度对比)
| 特点 | InheritedWidget | Provider | Riverpod |
|---|---|---|---|
| 依赖跟踪 | 手动 depend | 自动 depend | 自动 depend |
| 更新方式 | rebuild 整个 widget | 通常 listening 的才 rebuild | 精准范围更小 |
| 状态存储 | 无(需自己维护 State) | ChangeNotifier 等 | 独立的 ProviderContainer |
| 性能 | ★★★★★(底层) | ★★★★☆ | ★★★★★(更细粒度) |
| 异步管理 | 无 | 有(FutureProvider) | 超强(async/stream) |
| 灵活度 | 较低 | 中 | 最高 |
总结
- Provider 是 InheritedWidget 的封装
- Riverpod 是下一代 Provider,更灵活且不依赖 BuildContext
5️⃣ 如何自定义一个高性能 InheritedWidget(完整示例)
Step 1:创建 InheritedWidget
class CounterState extends InheritedWidget {
final int count;
const CounterState({
required this.count,
required Widget child,
}) : super(child: child);
static CounterState of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<CounterState>()!;
}
@override
bool updateShouldNotify(covariant CounterState oldWidget) {
return oldWidget.count != count;
}
}
Step 2:使用
class CounterView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counter = CounterState.of(context).count;
return Text('$counter');
}
}
测试:
- CounterView 会因 count 变化自动 rebuild
- 不用 setState,不用全局变量
6️⃣ 常见误区与优化技巧
❌ 误区 1:InheritedWidget 本身会 rebuild
错,它是 immutable,因此:
- InheritedWidget 不会主动 rebuild
- rebuilding 是对 child widget 生效的
❌ 误区 2:Any child widget 都会 rebuild
只有 调用 dependOnInheritedWidgetOfExactType 的 widget 才会
❌ 误区 3:shouldNotify 不重要
shouldNotify 决定是否通知所有 dependent,如果写 false:
@override
bool updateShouldNotify(_) => false;
依赖它的 widget 永远不会 rebuild。
✔ 最佳实践
如果某个字段变化,但并非所有子 widget 都需要重建,可以:
- 使用多层 InheritedWidget
- 或将不同数据分成不同 InheritedWidget
- 或使用 provider / riverpod 提升粒度
🎯 结语:为什么要搞得这么复杂?
因为 Flutter 的性能哲学是:
“尽可能让 widget rebuild,但尽可能让 rebuild 是局部且精准的。”
InheritedWidget 就是这个哲学的核心基石。
Provider / Riverpod 都是在它之上构建的生态能力。