一、前言
在flutter中一切皆为widget,许许多多的widget组成一个庞大的widget树。每个widget除了各司其职外,彼此之间还需要相互协作,相互通讯。所谓的通讯也即是一种数据共享与传递的手段。那么flutter中有哪些数据共享的手段呢?
flutter为我们提供了四种数据共享的方案:
- InheritedWidget:层级传递,适用于父组件传递给子组件的场景, 可跨层级。
- Notification:消息通知,适用于子组件通知父组件数据改变的场景。
- Event Bus:事件广播,可适用于各种类型的数据通知同步。
- Provider:状态管理,适用于复杂应用,可以方便的实现三种类型的数据同步。
这四种数据共享方案没有利弊之说,主要看适用的场景。
今天,我们重点看下InheritedWidget。它能够提供一种自上而下的数据共享方式,并且能够做到跨越层级。
二、InheritedWidget的使用
InheritedWidget是一个功能型组件(flutter中的widget分为功能型、组合型、绘制型)。功能型组件一般用于实现数据间的共享,如Theme、MediaQuery都是基于InheritedWidget实现的组件。
InheritedWidget使用起来也比较简单,它本身是一个抽象类,只需编写一个实现类即可。
/// InheritedWidget的实现类
class CountInheritedWidget extends InheritedWidget {
const CountInheritedWidget({Key? key, this.data, required Widget child})
: super(key: key, child: child);
/// 共享的数据
final int? data;
/// 返回当前组件的实例
static CountInheritedWidget? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<CountInheritedWidget>();
}
/// 数据更新的开关,返回true才能通知子组件更新
@override
bool updateShouldNotify(covariant CountInheritedWidget oldWidget) {
return oldWidget.data != data;
}
}
- CountInheritedWidget构造函数需要增加一个child必选属性,并传给父类。
- 同时需要定义一个of静态方法,通过context.dependOnInheritedWidgetOfExactType返回当前实例,以此来获取组件内部共享的数据data。
- updateShouldNotify是重写父类方法,它的作用是为我们提供一个数据更新的开关,返回false,data属性状态变化将不会同步到其它子组件上去。
/// 定义子类
class ChildWidget extends StatefulWidget {
const ChildWidget({Key? key}) : super(key: key);
@override
State<ChildWidget> createState() => _ChildWidgetState();
}
class _ChildWidgetState extends State<ChildWidget> {
@override
Widget build(BuildContext context) {
return Text(
/// 通过CountInheritedWidget的静态方法of获取共享数据data
'共享数据:${CountInheritedWidget.of(context)?.data}',
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
debugPrint('更新了!');
}
}
/// 定义父类
class ParentWidget extends StatefulWidget {
const ParentWidget({Key? key}) : super(key: key);
@override
State<ParentWidget> createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
/// 共享数据
int data = 1;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('InheritedWidget demo'),
),
/// CountInheritedWidget的用法
body: CountInheritedWidget(
data: data,
child: const ChildWidget(),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
/// data数据改变,会同步通知到子组件
data++;
});
},
child: const Icon(Icons.add),
),
);
}
}
InheritedWidget为单向数据流,数据只能自上而下,因此共享的数据需要定义到最顶层上。
当data数据改变时,子组件凡是使用到共享的数据,都会进行更新,并且会触发子组件的didChangeDependencies函数。
三、InheritedWidget的原理
InheritedWidget的使用比较简单,那么组件之间的数据是如何做到共享的呢?
带着这个问题,我们来分析下背后的实现原理。
widget树在首次构建的过程中,加载了InheritedWidget组件,会对应的创建InheritedElement。
abstract class InheritedWidget extends ProxyWidget {
/// ...
@override
InheritedElement createElement() => InheritedElement(this);
/// ...
}
所以,InheritedElement才是InheritedWidget核心所在,我们要重点分析它。
class InheritedElement extends ProxyElement {
/// ...
@override
void _updateInheritance() {
/// 获取父级inheritedWidgets集合
final Map<Type, InheritedElement>? incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
/// 将父级inheritedWidgets保存到当前_inheritedWidgets属性中
_inheritedWidgets = HashMap<Type, InheritedElement>.of(incomingWidgets);
else
/// 为null再创建新的集合
_inheritedWidgets = HashMap<Type, InheritedElement>();
/// 将自身添加到_inheritedWidgets中
_inheritedWidgets![widget.runtimeType] = this;
}
/// ...
}
updateInheritance方法在InheritedElement首次挂载的时候会调用。它的作用就是把所有的InheritedElement都进行收集,并存储在每一个InheritedElement中。
这样的设计可以保证每一层Element都包含同样的inheritedWidgets集合,这便是inheritedWidgets能够一直向下传递的原因。
那么它又是如何获取InheritedWidget中的数据呢?
还记得我们获取共享数据的时候调用了of静态方法,它实际调用的是context.dependOnInheritedWidgetOfExactType。context由外部传入,它代表了当前组件的上下文,即widget所对应的Elememt。所以dependOnInheritedWidgetOfExactType方法是在Element中定义的。
/// Element
@override
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
if (ancestor != null) {
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
_dependencies ??= HashSet<InheritedElement>();
_dependencies!.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
通过分析,调用dependOnInheritedWidgetOfExactType方法会从_inheritedWidgets获取到当前的InheritedWidget,并将它加入_dependencies依赖列表中,最后返回widget对象。
当我们更新共享的数据时,它又是如何通知到依赖组件呢?
在StatefulWidget中进行数据刷新都需要调用setState方法,然后会更新widget树中对应的组件。InheritedWidget也在更新的列表,它最终会触发InheritedElement的updated方法。
/// InheritedElement
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
/// 调用父级的updated方法
super.updated(oldWidget);
}
/// ComponentElement
@protected
void updated(covariant ProxyWidget oldWidget) {
/// 子类InheritedElement重写了notifyClients,所以实际调用的是子类的notifyClients
notifyClients(oldWidget);
}
/// InheritedElement
@override
void
(InheritedWidget oldWidget) {
/// 遍历依赖列表进行更新
for (final Element dependent in _dependents.keys) {
notifyDependent(oldWidget, dependent);
}
}
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
在updated中调用widget.updateShouldNotify判断是否需要更新数据,也即是我们重写的updateShouldNotify方法。
notifyClients遍历_dependents依赖列表进行InheritedElement更新,并且调用didChangeDependencies方法。
总结
1、flutter中有四种数据共享方案InheritedWidget、Notification、Event Bus、Provider。
2、InheritedWidget的使用需要编写一个实现类,除了提供child属性,还需要定义获取数据的方法,通常需要调用context.dependOnInheritedWidgetOfExactType方法返回当前widget对象,最后重写updateShouldNotify可以用于控制数据刷新时是否需要通知子组件。
3、InheritedWidget的核心原理在于:
-
widget树构建时收集所有InheritedElement对象并添加在其_inheritedWidgets属性中,这样就可以实现数据层层传递,达到共享的目的。
-
而通过dependOnInheritedWidgetOfExactType方法获取数据主要是从_inheritedWidgets属性中获取当前InheritedWidget返回,并将其加入dependents依赖列表。
-
共享数据改变时,先是判断updateShouldNotify是否为true,再进行更新。更新的方式则是遍历dependents依赖列表进行逐项调用widget的didChangeDependencies方法。因此也能解释了didChangeDependencies只有在子组件依赖父组件数据变化时才会调用。