前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flutter 组件集录 | InheritedModel 共享模型

Flutter 组件集录 | InheritedModel 共享模型

作者头像
张风捷特烈
发布2024-03-09 08:01:35
720
发布2024-03-09 08:01:35
举报
InheritedModel .png
InheritedModel .png

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

代码语言:javascript
复制
enum CounterAspect { color, value }

2. 创建 InheritedModel 派生类

和 InheritedWidget 一样,InheritedModel 也是一个抽象类。所以必须定义派生类来使用。如下:

  • 1. 定义 CounterModel 继承自 InheritedModel ,泛型指定为之前定义的 CounterAspect 枚举。
  • 2. InheritedModel 中持有需要共享的数据 color 和 counter。
  • 3. 定义 of 方法,根据上下文和方面,获取 CounterModel 对象。
  • 4. 复写 updateShouldNotify 控制更新通知的条件。
  • 5. 复写 updateShouldNotifyDependent 控制通知依赖变化的条件。
代码语言:javascript
复制
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
1709853748366.png

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 回调。

代码语言:javascript
复制
---->[修改颜色时]----
flutter: ======BoxDecorationWrap#didChangeDependencies=========
flutter: ======CounterText#didChangeDependencies=========

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

5. updateShouldNotifyDependent 方法

通过方面来控制通知依赖变化的核心是 updateShouldNotifyDependent 方法,它会回调旧的 CounterModelCounterAspect 集合 。这里的逻辑是:

  • 当颜色数据改变并且依赖颜色方面时,返回 true 。
  • 当数字数据改变并且依赖数字方面时,返回 true 。
代码语言:javascript
复制
@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 设置依赖元素和方面值的映射关系:

代码语言:javascript
复制
@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 组件,敬请期待 ~

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-03-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. Aspect 是什么
  • 2. 创建 InheritedModel 派生类
  • 3. 使用 InheritedModel
  • 4. InheritedModel 的价值
  • 5. updateShouldNotifyDependent 方法
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档