Flutter 组件集录 | InheritedModel 共享模型


theme: cyanosis

InheritedModel .png

上一篇 《Flutter 组件集录 | InheritedWidget 共享数据》介绍了 InheritedWidget 对 跨节点共享数据 的价值。本篇看一下 Flutter 源码中基于 InheritedWidget 实现的 InheritedModel 组件。它通过定义 Aspect(方面) 来更精细地控制依赖更新的粒度。

本组件案例已收录到 FlutterUnit:源码可详见 【InheritedModel/node1.dart】


1. Aspect 是什么

对于上一篇中的案例,交互中的功能需求是:

  • 点击下面的颜色,修改 B 的四周阴影颜色、以及 C 的文字颜色。
  • 点击加减按钮增加和减小 C 中的数字。

47.gif

这里颜色、文字就是需求中状态变化的两个方面。其中数字的变化和 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 数据:

1709853748366.png

C 组件需要访问颜色和数字两个数据,就通过两个方面进行获取:

image.png


4. InheritedModel 的价值

我们可以在 BuildOwner#buildScope 方法中调试分析交互过程脏表的信息。如下所示,当颜色发生变化,B 和 C 对应的元素会加入脏表。因为两者都依赖了 CounterModel 的颜色方面。

image.png

当数字发生变化,只有 C 对应的元素会加入脏表。因为 B 仅依赖了颜色方面,数字方面的数据变化,不会使 B 被通知。这就是和 InheritedWidget 最大的不同点,也足以见得 InheritedModel 可以通过 Aspect 对数据的变化进行更精细的控制。

image.png


依赖变化的通知,会触发 Element#didChangeDependencies, 并将自己标脏等待被收集重建。 在案例代码层面 StatelessWidget 无法感知到这个过程。对于 测试代码 来说, 我们可以将 B、C 改为 StatefulWidget,通过 State 的生命周期变化,感知对应 Element 的生命周期变化 (仅测试查看效果)。

如下,复写 B 和 C 状态类的 didChangeDependencies,然后分别更新颜色和数值。通过输出结果也能看出,只修改数字时,B 的状态类不会触发 didChangeDependencies 回调。

image.png

``` ---->[修改颜色时]---- flutter: ======BoxDecorationWrap#didChangeDependencies========= flutter: ======CounterText#didChangeDependencies=========

---->[修改数字时]---- flutter: ======CounterText#didChangeDependencies========= ```


5. updateShouldNotifyDependent 方法

通过方面来控制通知依赖变化的核心是 updateShouldNotifyDependent 方法,它会回调旧的 CounterModelCounterAspect 集合 。这里的逻辑是: - 当颜色数据改变并且依赖颜色方面时,返回 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

image.png

在 of 访问数据时,底层会通过 context.dependOnInheritedElement 获取 InheritedModel 对象。其中会建立依赖关系, 父级会触发 updateDependencies 方法更新依赖关系:

image.png

在 InheritedModelElement 中,复写了 updateDependencies 方法,通过 setDependencies 设置依赖元素和方面值的映射关系:

image.png

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 在源码中有三处使用场景,分别是 MeduaQuerySharedAppModelTimePicker:

image.png

大喵在 《Flutter 小技巧之 3.10 全新的 MediaQuery 优化与 InheritedModel》 一文中介绍过 MediaQuery 中 InheritedModel 作用。看完本文后,趁热打铁,可以去那里串串门。那本文就到这里,下一篇将介绍一下源码中基于 InheritedModel 首先得应用级键值对数据共享的 SharedAppModel 组件,敬请期待 ~

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值