Flutter 组件集录 | InheritedWidget 共享数据
1. 数据的跨节点共享的痛点
在 Flutter 应用开发中,数据的跨节点共享是一个非常重要的事。下面通过一个例子说明一下:
案例已收录在 FlutterUnit 中: 【InheritedWidget/node2_use.dart】
- A 组件状态类中有
color
和counter
两个数据。(红框整体) - B 是 A 的下层组件节点,需要依赖 A 中的 color 数据。(蓝框装饰盒)
- C 是 B 的下层组件节点,需要依赖 A 中的 color、counter 数据。(绿框数字)
在 A 状态类中处理交互事件:
- 点击下面的颜色,修改 B 的四周阴影颜色、以及 C 的文字颜色。
- 点击加减按钮增加和减小 C 中的数字。
这就是一个非常典型的组件间数据共享的问题:
- 上层节点的数据需要被下层节点访问。
- 上层节点更新时需要通知下层节点更新。
很常见的一种做法是通过构造函数传递参数,当 A 数据变化时重新新构建,传入 B、C 中的参数也发生变化,因此 B、C 组件可以随着 A 组件中的交互,而更新数据。
但是这样做当层级差距很大,参数传递链就会非常长。如下所示,如果下层有个 F 组件需要访问颜色值,而 D 、E、G 没有访问数据的需求。此时如果靠参数传递来共享数据就会非常糟糕,D 、E、G 不得不为了向 F 传参而被迫需要入参。
其实 Flutter 框架内部有类似的场景,比如全局主题色、字体、语言数据的改变。需要通知下层全部的节点进行更新。源码中不可能为所有的组件都通过构造来传递这些主题数据,那么下层的组件是如何访问到主题数据
,主题数据的更新又为什么有能力 通知所有组件触发更新
呢?
2. InheritedWidget 组件 - 数据跨节点共享方案
InheritedWidget 一个存储数据的仓库,提供了一种 订阅-通知 的数据访问方式。如下所示,下层组件并非被动接受数据,而是 主动请求数据。请求数据的组件,将会和 InheritedWidget 建立依赖关系,当数据发生变化重新构建时,会通知所有依赖组件对应的元素发生变化 (Element#didChangeDependencies)。
如下所示,创建 InheritedCounter
继承自 InheritedWidget
。
- 这里需要共享 Color 和 int 两个数据,表示颜色和数字。
- 提供 of 静态方法,通过上下文寻找上层的 InheritedCounter 并 建立依赖关系。
- 复写
updateShouldNotify
方法确定更新通知的条件。
class InheritedCounter extends InheritedWidget {
const InheritedCounter({
super.key,
this.color,
this.counter,
required super.child,
});
final Color? color;
final int? counter;
static InheritedCounter? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<InheritedCounter>();
}
@override
bool updateShouldNotify(InheritedCounter oldWidget) {
return color != oldWidget.color || counter != oldWidget.counter;
}
}
在 A 状态类中通过 InheritedCounter
包裹 B ,这样其下层的 B、C 节点就可以通过上下文访问 InheritedCounter
中存储的数据。
如下 B 组件在 build 方法中,通过 InheritedCounter.of
访问 InheritedCounter 颜色数据:
class BoxDecorationWrap extends StatelessWidget {
const BoxDecorationWrap({super.key});
@override
Widget build(BuildContext context) {
final Color color = InheritedCounter.of(context)?.color ?? Colors.black;
return Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: CounterText(),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: color),
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: color,
spreadRadius: 2,
blurRadius: 8,
offset: Offset(0, 0))
]),
);
}
}
C 组件在 build 方法中,通过 InheritedCounter.of
访问 InheritedCounter 颜色和数字数据:
class CounterText extends StatelessWidget {
const CounterText({super.key});
@override
Widget build(BuildContext context) {
final Color? color = InheritedCounter.of(context)?.color;
final int counter = InheritedCounter.of(context)?.counter??0;
return Text(
"Counter = $counter",
style: TextStyle(color: color,fontWeight: FontWeight.bold),
);
}
}
3. InheritedWidget 的通知更新
InheritedWidget 本身并没有更新自身数据的能力,需要借由外界来更新数据。比如 A 状态类中,选择颜色时通过 setState
触发更新通知,从而使 InheritedCounter 的数据发生变化:
void _onSelectColor(Color value) {
setState(() {
_color = value;
});
}
在 A 状态类对应元素更新的过程中,InheritedCounter 对应的 InheritedElement
会通知所有的依赖元素依赖发生变化。通过调试分析 BuildOwner.buildScope 方法,可以发现 BoxDecorationWrap、CounterText 组件对应的元素会被加到脏表中,被重新构建:
InheritedWidget 在更新过程中,只会更新依赖的组件。这点也让 InheritedWidget 更加优雅。比如上面 F 节点需要依赖颜色,那么 InheritedCounter 子树更新时,只有 B、C、F 被加入脏表。
4. updateShouldNotify 控制通知条件
updateShouldNotify 可以控制子树更新的条件,这里只有 InheritedCounter 更新前后颜色或数字不同的才允许通知。比如再加个按钮只是触发 setState ,两个数据都保持不变。此时 updateShouldNotify 返回 false。就不会通知依赖者们更新,这也是很合理的。
从源码的角度来说,当 InheritedElement 更新时,会先校验 updateShouldNotify 方法。如果通过才处理 super.updated(oldWidget)
。也就是说,当 updateShouldNotify 返回 false,此时 InheritedElement 更新相当于空过。下面的子树就不会进行任何更新处理,从而节约更新成本:
super.updated(oldWidget)
中,会通过 notifyClients
方法通知依赖者的更新:
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
其中 notifyDependent
会触发元素的 Element#didChangeDependencies
Element#didChangeDependencies
会触发 markNeedsBuild 方法将元素标脏在后期加入脏表。这也是 BuildOwner.buildScope 方法中, BoxDecorationWrap、CounterText 组件对应的元素会被加到脏表的根本原因。
到这里,我们认识了 InheritedWidget 组件真正的价值。它很好地解决了 数据的跨节点共享的痛点,也为 Provider 状态管理中数据的跨节点共享提供了理论基础。正确清晰地理解 InheritedWidget 的价值,对一位 Flutter 开发者来说至关重要。那本文就到这里,后面还会介绍 Flutter 框架中,在 InheritedWidget 基础上,提供的各种使用组件。谢谢观看,我们下次再见 ~
转载自:https://juejin.cn/post/7343161506698117130