Flutter 组件集录 | InheritedModel 共享模型
本组件案例已收录到 FlutterUnit:源码可详见 【InheritedModel/node1.dart】
1. Aspect 是什么
对于上一篇中的案例,交互中的功能需求是:
- 点击下面的颜色,修改 B 的四周阴影颜色、以及 C 的文字颜色。
- 点击加减按钮增加和减小 C 中的数字。
这里颜色、文字就是需求中状态变化的两个方面。其中数字的变化和 B 的阴影颜色无关。如果使用 InheritedWidget 实现数据共享,那么数字的变化也会通知 B 组件对应的元素,依赖数据发生变化。
InheritedModel 相比于 InheritedWidget,其功能在于:允许为不同维度的数据定义 Aspect。比如这里定义 CounterAspect 有颜色和数值两个方面,当 B 只访问颜色方面的数据时,那数字方面的更新,就不会触发 B 对应的元素的 didChangeDependencies :
enum CounterAspect { color, value }
2. 创建 InheritedModel 派生类
和 InheritedWidget 一样,InheritedModel 也是一个抽象类。所以必须定义派生类来使用。如下:
- [1]. 定义
CounterModel
继承自InheritedModel
,泛型指定为之前定义的 CounterAspect 枚举。 - [2]. InheritedModel 中持有需要共享的数据 color 和 counter。
- [3]. 定义 of 方法,根据上下文和方面,获取 CounterModel 对象。
- [4]. 复写 updateShouldNotify 控制更新通知的条件。
- [5]. 复写 updateShouldNotifyDependent 控制通知依赖变化的条件。
class CounterModel extends InheritedModel<CounterAspect> {
const CounterModel({
super.key,
this.color,
this.counter,
required super.child,
});
final Color? color;
final int? counter;
static CounterModel? of(BuildContext context,CounterAspect aspect){
return InheritedModel.inheritFrom<CounterModel>(context, aspect: aspect);
}
@override
bool updateShouldNotify(CounterModel oldWidget) {
return color != oldWidget.color || counter != oldWidget.counter;
}
@override
bool updateShouldNotifyDependent(CounterModel oldWidget, Set<CounterAspect> dependencies) {
if (color != oldWidget.color && dependencies.contains(CounterAspect.color)) {
return true;
}
if (counter != oldWidget.counter && dependencies.contains(CounterAspect.value)) {
return true;
}
return false;
}
}
3. 使用 InheritedModel
InheritedModel 是在 InheritedWidget 基础上拓展的加强版,在使用方式上也非常类似:如下 B 组件只需要访问颜色,就通过 CounterModel.of
根据上下文和颜色方面获取 CounterModel 对象,在得到 color 数据:
C 组件需要访问颜色和数字两个数据,就通过两个方面进行获取:
4. InheritedModel 的价值
我们可以在 BuildOwner#buildScope 方法中调试分析交互过程脏表的信息。如下所示,当颜色发生变化,B 和 C 对应的元素会加入脏表。因为两者都依赖了 CounterModel 的颜色方面。
当数字发生变化,只有 C 对应的元素会加入脏表。因为 B 仅依赖了颜色方面,数字方面的数据变化,不会使 B 被通知。这就是和 InheritedWidget 最大的不同点,也足以见得 InheritedModel 可以通过 Aspect 对数据的变化进行更精细的控制。
依赖变化的通知,会触发 Element#didChangeDependencies
, 并将自己标脏等待被收集重建。 在案例代码层面 StatelessWidget 无法感知到这个过程。对于 测试代码 来说, 我们可以将 B、C 改为 StatefulWidget,通过 State 的生命周期变化,感知对应 Element 的生命周期变化 (仅测试查看效果)。
如下,复写 B 和 C 状态类的 didChangeDependencies
,然后分别更新颜色和数值。通过输出结果也能看出,只修改数字时,B 的状态类不会触发 didChangeDependencies 回调。
---->[修改颜色时]----
flutter: ======BoxDecorationWrap#didChangeDependencies=========
flutter: ======CounterText#didChangeDependencies=========
---->[修改数字时]----
flutter: ======CounterText#didChangeDependencies=========
5. updateShouldNotifyDependent 方法
通过方面来控制通知依赖变化的核心是 updateShouldNotifyDependent 方法,它会回调旧的 CounterModel
和 CounterAspect 集合
。这里的逻辑是:
- 当颜色数据改变并且依赖颜色方面时,返回 true 。
- 当数字数据改变并且依赖数字方面时,返回 true 。
@override
bool updateShouldNotifyDependent(CounterModel oldWidget, Set<CounterAspect> dependencies) {
if (color != oldWidget.color && dependencies.contains(CounterAspect.color)) {
return true;
}
if (counter != oldWidget.counter && dependencies.contains(CounterAspect.value)) {
return true;
}
return false;
}
通过 InheritedModel 源码可以看出,只有当 updateShouldNotifyDependent 通过时,才会触发依赖元素的 didChangeDependencies
:
在 of 访问数据时,底层会通过 context.dependOnInheritedElement
获取 InheritedModel 对象。其中会建立依赖关系, 父级会触发 updateDependencies
方法更新依赖关系:
在 InheritedModelElement 中,复写了 updateDependencies
方法,通过 setDependencies
设置依赖元素和方面值的映射关系:
@override
void updateDependencies(Element dependent, Object? aspect) {
final Set<T>? dependencies = getDependencies(dependent) as Set<T>?;
if (dependencies != null && dependencies.isEmpty) {
return;
}
if (aspect == null) {
setDependencies(dependent, HashSet<T>());
} else {
assert(aspect is T);
/// 方面非空时,通过 setDependencies 维护映射关系
setDependencies(dependent, (dependencies ?? HashSet<T>())..add(aspect as T));
}
}
updateDependencies 和 updateShouldNotifyDependent 两个方法就是 InheritedModelElement 的全部源码内容。总的来看 InheritedModel 的作用是非常纯粹的,就是通过 方面 Aspect 来控制更新依赖通知的粒度。InheritedModel 在源码中有三处使用场景,分别是 MeduaQuery
、SharedAppModel
、TimePicker
:
转载自:https://juejin.cn/post/7343530251152556086