theme: cyanosis
上一篇 《Flutter 组件集录 | InheritedWidget 共享数据》介绍了 InheritedWidget 对 跨节点共享数据 的价值。本篇看一下 Flutter 源码中基于 InheritedWidget 实现的 InheritedModel 组件。它通过定义 Aspect(方面) 来更精细地控制依赖更新的粒度。
本组件案例已收录到 FlutterUnit:源码可详见 【InheritedModel/node1.dart】
1. Aspect 是什么
对于上一篇中的案例,交互中的功能需求是:
- 点击下面的颜色,修改 B 的四周阴影颜色、以及 C 的文字颜色。
- 点击加减按钮增加和减小 C 中的数字。
这里颜色、文字就是需求中状态变化的两个方面。其中数字的变化和 B 的阴影颜色无关。如果使用 InheritedWidget 实现数据共享,那么数字的变化也会通知 B 组件对应的元素,依赖数据发生变化。
InheritedModel 相比于 InheritedWidget,其功能在于:允许为不同维度的数据定义 Aspect。比如这里定义 CounterAspect 有颜色和数值两个方面,当 B 只访问颜色方面的数据时,那数字方面的更新,就不会触发 B 对应的元素的 didChangeDependencies :
dart enum CounterAspect { color, value }
2. 创建 InheritedModel 派生类
和 InheritedWidget 一样,InheritedModel 也是一个抽象类。所以必须定义派生类来使用。如下:
- [1]. 定义
CounterModel
继承自InheritedModel
,泛型指定为之前定义的 CounterAspect 枚举。 - [2]. InheritedModel 中持有需要共享的数据 color 和 counter。
- [3]. 定义 of 方法,根据上下文和方面,获取 CounterModel 对象。
- [4]. 复写 updateShouldNotify 控制更新通知的条件。
- [5]. 复写 updateShouldNotifyDependent 控制通知依赖变化的条件。
```dart class CounterModel extends InheritedModel { 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 (context, aspect: aspect); }
@override bool updateShouldNotify(CounterModel oldWidget) { return color != oldWidget.color || counter != oldWidget.counter; }
@override bool updateShouldNotifyDependent(CounterModel oldWidget, Set 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 。
dart @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
设置依赖元素和方面值的映射关系:
dart @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
:
大喵在 《Flutter 小技巧之 3.10 全新的 MediaQuery 优化与 InheritedModel》 一文中介绍过 MediaQuery 中 InheritedModel 作用。看完本文后,趁热打铁,可以去那里串串门。那本文就到这里,下一篇将介绍一下源码中基于 InheritedModel 首先得应用级键值对数据共享的 SharedAppModel 组件,敬请期待 ~