Flutter 组件集录 | InheritedWidget 共享数据


theme: cyanosis

InheritedWidget.png

1. 数据的跨节点共享的痛点

在 Flutter 应用开发中,数据的跨节点共享是一个非常重要的事。下面通过一个例子说明一下:

案例已收录在 FlutterUnit 中: 【InheritedWidget/node2_use.dart】

  • A 组件状态类中有 colorcounter 两个数据。(红框整体)
  • B 是 A 的下层组件节点,需要依赖 A 中的 color 数据。(蓝框装饰盒)
  • C 是 B 的下层组件节点,需要依赖 A 中的 color、counter 数据。(绿框数字)

image.png

在 A 状态类中处理交互事件:

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

47.gif


这就是一个非常典型的组件间数据共享的问题:

  • 上层节点的数据需要被下层节点访问
  • 上层节点更新时需要通知下层节点更新

很常见的一种做法是通过构造函数传递参数,当 A 数据变化时重新新构建,传入 B、C 中的参数也发生变化,因此 B、C 组件可以随着 A 组件中的交互,而更新数据。

image.png

但是这样做当层级差距很大,参数传递链就会非常长。如下所示,如果下层有个 F 组件需要访问颜色值,而 D 、E、G 没有访问数据的需求。此时如果靠参数传递来共享数据就会非常糟糕,D 、E、G 不得不为了向 F 传参而被迫需要入参。

image.png

其实 Flutter 框架内部有类似的场景,比如全局主题色、字体、语言数据的改变。需要通知下层全部的节点进行更新。源码中不可能为所有的组件都通过构造来传递这些主题数据,那么下层的组件是如何访问到主题数据,主题数据的更新又为什么有能力 通知所有组件触发更新 呢?


2. InheritedWidget 组件 - 数据跨节点共享方案

InheritedWidget 一个存储数据的仓库,提供了一种 订阅-通知 的数据访问方式。如下所示,下层组件并非被动接受数据,而是 主动请求数据。请求数据的组件,将会和 InheritedWidget 建立依赖关系,当数据发生变化重新构建时,会通知所有依赖组件对应的元素发生变化 (Element#didChangeDependencies)。

image.png


如下所示,创建 InheritedCounter 继承自 InheritedWidget

  • 这里需要共享 Color 和 int 两个数据,表示颜色和数字。
  • 提供 of 静态方法,通过上下文寻找上层的 InheritedCounter建立依赖关系
  • 复写 updateShouldNotify 方法确定更新通知的条件。

```dart class InheritedCounter extends InheritedWidget { const InheritedCounter({ super.key, this.color, this.counter, required super.child, });

final Color? color; final int? counter;

static InheritedCounter? of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType (); }

@override bool updateShouldNotify(InheritedCounter oldWidget) { return color != oldWidget.color || counter != oldWidget.counter; } } ```

在 A 状态类中通过 InheritedCounter 包裹 B ,这样其下层的 B、C 节点就可以通过上下文访问 InheritedCounter 中存储的数据。

image.png

如下 B 组件在 build 方法中,通过 InheritedCounter.of 访问 InheritedCounter 颜色数据:

```dart class BoxDecorationWrap extends StatelessWidget { const BoxDecorationWrap({super.key});

@override Widget build(BuildContext context) { final Color color = InheritedCounter.of(context)?.color ?? Colors.black; return Container( padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: CounterText(), decoration: BoxDecoration( color: Colors.white, border: Border.all(color: color), borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( color: color, spreadRadius: 2, blurRadius: 8, offset: Offset(0, 0)) ]), ); } } ```

C 组件在 build 方法中,通过 InheritedCounter.of 访问 InheritedCounter 颜色和数字数据:

```dart class CounterText extends StatelessWidget { const CounterText({super.key});

@override Widget build(BuildContext context) { final Color? color = InheritedCounter.of(context)?.color; final int counter = InheritedCounter.of(context)?.counter??0; return Text( "Counter = $counter", style: TextStyle(color: color,fontWeight: FontWeight.bold), ); } } ```


3. InheritedWidget 的通知更新

InheritedWidget 本身并没有更新自身数据的能力,需要借由外界来更新数据。比如 A 状态类中,选择颜色时通过 setState 触发更新通知,从而使 InheritedCounter 的数据发生变化:

dart void _onSelectColor(Color value) { setState(() { _color = value; }); }

在 A 状态类对应元素更新的过程中,InheritedCounter 对应的 InheritedElement会通知所有的依赖元素依赖发生变化。通过调试分析 BuildOwner.buildScope 方法,可以发现 BoxDecorationWrap、CounterText 组件对应的元素会被加到脏表中,被重新构建:

image.png

InheritedWidget 在更新过程中,只会更新依赖的组件。这点也让 InheritedWidget 更加优雅。比如上面 F 节点需要依赖颜色,那么 InheritedCounter 子树更新时,只有 B、C、F 被加入脏表。

其原理就是 InheritedElement 内部建立的 订阅-通知 机制,通过 of 访问数据的元素(context),会被 InheritedElement 收集视为 订阅者,在 InheritedElement 更新时,会通知所有依赖者触发 didChangeDependencies 并加入脏表。这个在 《 Flutter 渲染机制 - 聚沙成塔》 中进行过源码级的探讨。


4. updateShouldNotify 控制通知条件

updateShouldNotify 可以控制子树更新的条件,这里只有 InheritedCounter 更新前后颜色或数字不同的才允许通知。比如再加个按钮只是触发 setState ,两个数据都保持不变。此时 updateShouldNotify 返回 false。就不会通知依赖者们更新,这也是很合理的。

image.png

从源码的角度来说,当 InheritedElement 更新时,会先校验 updateShouldNotify 方法。如果通过才处理 super.updated(oldWidget)。也就是说,当 updateShouldNotify 返回 false,此时 InheritedElement 更新相当于空过。下面的子树就不会进行任何更新处理,从而节约更新成本:

image.png

super.updated(oldWidget) 中,会通过 notifyClients 方法通知依赖者的更新:

dart @protected void updated(covariant ProxyWidget oldWidget) { notifyClients(oldWidget); } 其中 notifyDependent 会触发元素的 Element#didChangeDependencies

image.png

Element#didChangeDependencies 会触发 markNeedsBuild 方法将元素标脏在后期加入脏表。这也是 BuildOwner.buildScope 方法中, BoxDecorationWrap、CounterText 组件对应的元素会被加到脏表的根本原因。

image.png


到这里,我们认识了 InheritedWidget 组件真正的价值。它很好地解决了 数据的跨节点共享的痛点,也为 Provider 状态管理中数据的跨节点共享提供了理论基础。正确清晰地理解 InheritedWidget 的价值,对一位 Flutter 开发者来说至关重要。那本文就到这里,后面还会介绍 Flutter 框架中,在 InheritedWidget 基础上,提供的各种使用组件。谢谢观看,我们下次再见 ~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值