Flutter 的 InheritedWidget 是如何建立依赖关系的

54 阅读4分钟

✅ 一句话总览

依赖关系是在「子 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 相关框架都会秒懂。


📌 目录

  1. InheritedWidget 建立依赖的完整过程(源码级)
  2. 为什么 InheritedWidget 查找是 O(1)
  3. InheritedElement.notifyClients 的源码解析
  4. InheritedWidget vs Provider vs Riverpod(深度对比)
  5. 如何自定义一个高性能 InheritedWidget
  6. 常见误区与优化技巧

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(深度对比)

特点InheritedWidgetProviderRiverpod
依赖跟踪手动 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 都是在它之上构建的生态能力。