博客地址:https://www.jianshu.com/p/10a1a16dfff7

背景

原生提供了StatefulWidget这个有状态组件来管理状态,对于多组件的状态交互可以选择由父组件进行统一管理分发,但是当业务一旦复杂,组件树的分支足够多,会出现状态下沉过深入,状态传递复杂的问题。

简单情况是这样的:
状态管理背景1.png

随着功能的增加,你的应用程序将会有几十个甚至上百个状态。这个时候你的应用应该会是这样:

状态管理背景2.png
状态管理背景2.png

上述实际就是多个页面需要共享状态和传递信息场景下出现的,直接的做法是:

  1. 通过父widget来分发通知,有嵌套层级深的问题,父层级的setState导致不必要build问题

  2. 通过回调传递,同样存在传递深的问题 ,回调也会出现漏调用的问题

面临的问题

  1. 如何获取数据源

  2. 如何更新数据源

  3. 如何通知组件数据源更新

  4. 跨组件数据源如何共享

数据流概念

状态管理里会出现基于单向数据流的情况,这里先介绍下数据流的概念

单向数据流

单向数据流.png
单向数据流.png
  • state:驱动应用的数据源。

  • view:以声明方式将 state 映射到视图 。

  • actions:响应在 view 上的用户输入导致的状态变化

单向数据流的状态管理:通过定义和隔离状态管理中的各种概念并强制遵守一定的规则,我们的代码将会变得更结构化且易维护

特点:
(1) 所有状态的改变可记录、可跟踪,源头易追溯;
(2) 所有数据只有一份,组件数据只有唯一的入口和出口,使得程序更直观更容易理解,有利于应用的可维护性;
(3) 一旦数据变化,就去更新页面(data->页面),但是没有(页面->data);
(4) 如果用户在页面上做了变动,那么就手动收集起来(双向是自动),合并到原有的数据中

双向数据流

双向数据流.png
双向数据流.png

双向数据绑定,带来双向数据流,数据(state)和视图(View)之间的双向绑定。

ng 里的 ng-model 和 vue 里的 v-model,以及Android的DataBinding

说到底就是 (value 的单向绑定 + onChange 事件侦听)的一个语法糖

特点:
(1)无论数据改变,或是用户操作,都能带来互相的变动,自动更新。适用于项目细节,如:UI控件中(通常是类表单操作)。
(2)状态的改变不可控

解决方案

StatefulWidget

官方自带有状态组件,其组件里的各种状态可以由自身管理,也可由父组件管理,哪个管理合适,一般遵循以下原则:

  • 如果状态是用户数据,如复选框的选中状态、滑块的位置,则该状态最好由父Widget管理。
  • 如果状态是有关界面外观效果的,例如颜色、动画,那么状态最好由Widget本身来管理。
  • 如果某一个状态是不同Widget共享的则最好由它们共同的父Widget管理。

一般来说,在Widget内部管理状态封装性会好一些,而在父Widget中管理会比较灵活,如果不知道是不是该由widget自身管理,则优先设计为由父widget管理并将其设计为StatelessWidget

问题:

功能单一,复杂多页面情况下,状态下沉过于深入,状态传递复杂,rebuild的范围过大等

InherityWidget

功能型组件,提供了一种数据在widget树中从上到下传递、共享的机制

比如在根Widget通过一个InherityWidget共享了一个状态,那么便可以在任意子widget中获取它,Theme,Navigator等都是通过这种机制来共享给整个应用的,它省去了逐级传递的麻烦

使用

  1. 用于存储共享数据的父Widget,该widget继承InheritedWidget

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class FatherWidget extends InheritedWidget {
    final int data;

    FatherWidget({@required this.data, Widget child}) : super(child: child);

    //子树通过该方法获取共享数据
    static FatherWidget of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(FatherWidget);
    }

    //该回调决定当data发生变化时,是否通知子树中依赖data的widget
    @override
    bool updateShouldNotify(FatherWidget oldWidget) {
    return oldWidget.data != data;
    }
    }
  2. 子widget,获取状态和处理依赖发生变化时的响应

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class ChildWidget extends StatefulWidget {
    @override
    _ChildWidgetState createState() => _ChildWidgetState();
    }

    class _ChildWidgetState extends State<ChildWidget> {
    @override
    Widget build(BuildContext context) {
    return new Text(FatherWidget.of(context).data.toString());
    }

    @override
    void didChangeDependencies() {
    super.didChangeDependencies();
    //父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用
    //如果build中没有依赖InheritedWidget,则此回调不会被调用
    print("didChangeDependencies = " +
    FatherWidget.of(context).data.toString());
    }
    }
  3. 整合

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class ContainerWidget extends StatefulWidget {
    @override
    _ContainerWidgetState createState() => _ContainerWidgetState();
    }

    class _ContainerWidgetState extends State<ContainerWidget> {
    int _data = 0;

    void _incrementCounter() {
    setState(() {
    _data++; /// 改变状态
    });
    }

    @override
    Widget build(BuildContext context) {
    return FatherWidget(
    data: widget.data,
    child: ChildWidget(),
    );
    }

    }

图示

  1. 如何实现子树获取InheritedWidget
InheritedWidget机制1.png
InheritedWidget机制1.png
  1. InheritedWidget和用of获取过它的子Widget如何建立联系的
    InheritedWidget机制2.png
    InheritedWidget机制2.png

关键点

“didChangeDependencies”:

State里的生命周期函数之一,表示依赖发生变化时由Framework调用通知,这里的依赖指的是子widget是否使用了InherityWidget的数据

另外,此回调紧跟initState执行,这里可以直接.context获取来使用

源码

如何实现子树直接获取InheritedWidget

新建build InheritedWidget时,对应的Element会调用如下_updateInheritance方法,从父Element复制 _inheritedWidgets并将此InheritedElement注册进 _inheritedWidgets里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class InheritedElement extends ProxyElement {
final Map<Element, Object> _dependents = HashMap<Element, Object>();
...
@override
void _updateInheritance() {
...
final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
else
_inheritedWidgets = HashMap<Type, InheritedElement>();
_inheritedWidgets[widget.runtimeType] = this;
}
...
}

获取过程,如下

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
/// 调用of方法
static ThemeData of(BuildContext context, { bool shadowThemeOnly = false }) {
final _InheritedTheme inheritedTheme = context.inheritFromWidgetOfExactType(_InheritedTheme);
...
}

/// Element class
@override
InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
...
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType]; /// 根据类型 取出InheritedElement
if (ancestor != null) {
assert(ancestor is InheritedElement);
return inheritFromElement(ancestor, aspect: aspect); /// 此处建立子Element和InheritedElement的关系并返回InheritedWidget
}
_hadUnsatisfiedDependencies = true;
return null;
}

@override
InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
...
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}

注意:还有个方法是只取InheritedElement而不注册依赖关系的

1
2
3
4
5
6
7
/// Element class
@override
InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType) {
...
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
return ancestor;
}

总结

优点:

  1. 自动订阅

  2. 可跨组件获取状态

缺点:

  1. 每次促使inheritedWidget build重建 事实上都会触发所有子树的build,所以需要封装一个StatefulWidget来配合实现缓存加载

  2. 没有有效分离视图逻辑和业务逻辑。

  3. 无法定向通知/指向性通知。 事实上依赖InheriteWidget的子Widget,在调用State的didChangeDependencies前,在Element这一级会调用markNeedsBuild,所以都会rebuild一下

参考

  1. 单向数据流和双向数据流