Flutter 组件集录 | InheritedModel 共享模型

上一篇 《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 枚举。

  • 3\]. 定义 of 方法,根据上下文和方面,获取 CounterModel 对象。

  • 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 数据:

![1709853748366.png](https://file.jishuzhan.net/article/1765905263707033602/3627a2697b7a292e4c4aae46da7852bd.webp)

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

![image.png](https://file.jishuzhan.net/article/1765905263707033602/3faa56cc68e108d4c3bd861976fe76d3.webp)

*** ** * ** ***

#### 4. InheritedModel 的价值

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

![image.png](https://file.jishuzhan.net/article/1765905263707033602/fe11d14d853b3310092aef50cc54690d.webp)

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

![image.png](https://file.jishuzhan.net/article/1765905263707033602/d02d260eab7601333cb82ff82b489cbf.webp)

*** ** * ** ***

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

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

![image.png](https://file.jishuzhan.net/article/1765905263707033602/27a24817027fa97ebfb7b6cb9baae2e9.webp)

```diff
---->[修改颜色时]----
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

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

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

相关推荐
Chrome深度玩家1 小时前
谷歌翻译安卓版拍照翻译精准度与语音识别评测【轻松交流】
android·人工智能·语音识别
长点点2 小时前
从架构角度了解安卓APP(1):安卓核心组件的设计逻辑与演进
android·架构·app
用户71887350336802 小时前
Android适配最新SplashScreen方案
android·android jetpack
EQ-雪梨蛋花汤3 小时前
【Part 2安卓原生360°VR播放器开发实战】第二节|基于等距圆柱投影方式实现全景视频渲染
android·音视频·vr
Railshiqian3 小时前
Framework.jar里的类无法通过Class.forName反射某个类的问题排查
android·反射·framework.jar
用户42274481246213 小时前
flutter篇---Android gradle版本报错
flutter
鸿蒙布道师4 小时前
鸿蒙NEXT开发正则工具类RegexUtil(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
Anger重名了4 小时前
🌟 一篇文章搞懂Kotlin协程:比线程更轻量的并发神器
android
缘来的精彩4 小时前
adb常用的20个命令
android·adb·kotlin