Flutter状态管理:Provider

InheritedWidget

简介

InheritedWidget提供了一种在widget树中从上到下共享数据的方式,能实现组件跨级传递数据。比如我们在应用的根widget中通过InheritedWidget共享了一个数据,那么我们便可以在任意子widget中来获取该共享的数据!

应用:如Flutter SDK中正是通过InheritedWidget来共享ThemeLocal(当前语言环境)信息

数据传输

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class ShareDataWidget extends InheritedWidget {
final int data; //需要在子树中共享的数据,保存点击次数
const ShareDataWidget({
Key? key,
required this.data,
required Widget child,
}) : super(key: key, child: child);

//定义一个便捷方法,方便子树中的widget获取共享数据
static ShareDataWidget? of(BuildContext context, {bool listen = true}) {
ShareDataWidget? shareDataWidget;
if (listen) {
shareDataWidget =
context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
} else {
shareDataWidget =
context.getElementForInheritedWidgetOfExactType<ShareDataWidget>()
as ShareDataWidget?;
}
return shareDataWidget;
}

@override
bool updateShouldNotify(covariant ShareDataWidget oldWidget) {
return oldWidget.data != data;
}
}

原理:

  1. dependOnInheritedWidgetOfExactType方法将在下文解析

  2. getElementForInheritedWidgetOfExactType方法:

    1
    2
    3
    4
    5
    @override
    InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
    return ancestor;
    }

    通过上述两个函数既可以获取到InheritedElement实例,继而拿到了共享的数据

刷新机制

InheritedElement和Element之间有一些交互,实际上自带了一套刷新机制

  • InheritedElement存子节点Element:_dependents,这个变量用来存储需要刷新的子Element

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    class InheritedElement extends ProxyElement {
    InheritedElement(InheritedWidget widget) : super(widget);

    final Map<Element, Object?> _dependents = HashMap<Element, Object?>();

    @override
    void debugDeactivated() {
    assert(() {
    assert(_dependents.isEmpty);
    return true;
    }());
    super.debugDeactivated();
    }

    @protected
    Object? getDependencies(Element dependent) {
    return _dependents[dependent];
    }

    @protected
    void setDependencies(Element dependent, Object? value) {
    _dependents[dependent] = value;
    }

    @protected
    void updateDependencies(Element dependent, Object? aspect) {
    setDependencies(dependent, null);
    }
    }
  • InheritedElement刷新子Element

    1. notifyClients方法中,循环_dependents存储的Element,传入notifyDependent
    2. notifyDependent中,传入Element调用自身didChangeDependencies方法
    3. Element的didChangeDependencies方法会调用markNeedsBuild,来刷新自身
  • InheritedWidget的子节点是如何将自身Element添加到_dependents

    dependOnInheritedWidgetOfExactType()源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @override
    InheritedWidget dependOnInheritedWidgetOfExactType({ Object aspect }) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    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 }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
    }

可以看到在调用dependOnInheritedWidgetOfExactType时,InheritedWidget和依赖它的子widget注册了依赖关系,之后当InheritedWidget发生变化时,就会更新依赖它的子widget,也就是会调用这些子widget的didChangeDependencies()build()

  • dependOnInheritedElement方法中,会传入InheritedElement实例ancestor
  • ancestor会调用updateDependencies方法,将自身的Element实例传入,这样就添加到_dependents中了

    可以发现:调用dependOnInheritedWidgetOfExactType()getElementForInheritedWidgetOfExactType()的区别就是前者会注册依赖关系,而后者不会

didChangeDependencies

State的生命周期之一,它会在“依赖”发生变化时被Flutter框架调用,而这个“依赖”指的就是子widget是否使用了父widget中InheritedWidget的数据!如果使用了,则代表子widget有依赖;如果没有使用则代表没有依赖。这种机制可以使子widget在所依赖的InheritedWidget变化时来更新自身!在数据发生变化时只对使用该数据的widget更新是合理并且性能友好的。

应该在didChangeDependencies中做什么?

如果需要在依赖改变后执行一些昂贵的操作,比如网络请求,这时最好的方式就是在此方法中执行,这样可以避免每次build()都执行这些昂贵操作。

Provider

Provider是对InheritedWidget组件的上层封装,使其更易用,更易复用。

Provider原理

原理:基于发布者-订阅者模式,Model变化后会自动通知ChangeNotifierProvider(订阅者),ChangeNotifierProvider内部会重新构建InheritedWidget,而依赖该InheritedWidget的子widget就会更新。

使用Provider 优点:

  1. 业务代码更关注数据了,只要更新Model,则UI会自动更新,而不用在状态改变后去手动调用setState()来显示更新页面
  2. 数据改变的消息传递被屏蔽了,我们无需手动去处理状态改变事件的发布和订阅,这一切都被封装在Provider中了
  3. 在大型复杂应用中,尤其是需要全局共享的状态非常多时,使用Provider将会大大简化代码逻辑,降低出错的概率,提高开发效率

参考:

【源码篇】Flutter Provider的另一面(万字图文+插件)

数据共享(InheritedWidget)