背景
原生提供了StatefulWidget这个有状态组件来管理状态,对于多组件的状态交互可以选择由父组件进行统一管理分发,但是当业务一旦复杂,组件树的分支足够多,会出现状态下沉过深入,状态传递复杂的问题。
简单情况是这样的:
随着功能的增加,你的应用程序将会有几十个甚至上百个状态。这个时候你的应用应该会是这样:
上述实际就是多个页面需要共享状态和传递信息场景下出现的,直接的做法是:
通过父widget来分发通知,有嵌套层级深的问题,父层级的setState导致不必要build问题
通过回调传递,同样存在传递深的问题 ,回调也会出现漏调用的问题
面临的问题
如何获取数据源
如何更新数据源
如何通知组件数据源更新
跨组件数据源如何共享
数据流概念
状态管理里会出现基于单向数据流的情况,这里先介绍下数据流的概念
单向数据流
state:驱动应用的数据源。
view:以声明方式将 state 映射到视图 。
actions:响应在 view 上的用户输入导致的状态变化
单向数据流的状态管理:通过定义和隔离状态管理中的各种概念并强制遵守一定的规则,我们的代码将会变得更结构化且易维护
特点:
(1) 所有状态的改变可记录、可跟踪,源头易追溯;
(2) 所有数据只有一份,组件数据只有唯一的入口和出口,使得程序更直观更容易理解,有利于应用的可维护性;
(3) 一旦数据变化,就去更新页面(data->页面),但是没有(页面->data);
(4) 如果用户在页面上做了变动,那么就手动收集起来(双向是自动),合并到原有的数据中
双向数据流
双向数据绑定,带来双向数据流,数据(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等都是通过这种机制来共享给整个应用的,它省去了逐级传递的麻烦
使用
- 用于存储共享数据的父Widget,该widget继承InheritedWidget
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;
}
}
-
子widget,获取状态和处理依赖发生变化时的响应
class ChildWidget extends StatefulWidget { @override _ChildWidgetState createState() => _ChildWidgetState(); } class _ChildWidgetState extends State
{ @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()); } } -
整合
class ContainerWidget extends StatefulWidget { @override _ContainerWidgetState createState() => _ContainerWidgetState(); } class _ContainerWidgetState extends State
{ int _data = 0; void _incrementCounter() { setState(() { _data++; /// 改变状态 }); } @override Widget build(BuildContext context) { return FatherWidget( data: widget.data, child: ChildWidget(), ); } }
图示
- 如何实现子树获取InheritedWidget
-
InheritedWidget和用of获取过它的子Widget如何建立联系的
关键点
"didChangeDependencies":
State里的生命周期函数之一,表示依赖发生变化时由Framework调用通知,这里的依赖指的是子widget是否使用了InherityWidget的数据
另外,此回调紧跟initState执行,这里可以直接.context获取来使用
源码
如何实现子树直接获取InheritedWidget
新建build InheritedWidget时,对应的Element会调用如下_updateInheritance方法,从父Element复制 _inheritedWidgets并将此InheritedElement注册进 _inheritedWidgets里
class InheritedElement extends ProxyElement {
final Map _dependents = HashMap();
...
@override
void _updateInheritance() {
...
final Map incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = HashMap.from(incomingWidgets);
else
_inheritedWidgets = HashMap();
_inheritedWidgets[widget.runtimeType] = this;
}
...
}
获取过程,如下
/// 调用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();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
注意:还有个方法是只取InheritedElement而不注册依赖关系的
/// Element class
@override
InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType) {
...
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
return ancestor;
}
总结
优点:
自动订阅
可跨组件获取状态
缺点:
每次促使inheritedWidget build重建 事实上都会触发所有子树的build,所以需要封装一个StatefulWidget来配合实现缓存加载
没有有效分离视图逻辑和业务逻辑。
无法定向通知/指向性通知。 事实上依赖InheriteWidget的子Widget,在调用State的didChangeDependencies前,在Element这一级会调用markNeedsBuild,所以都会rebuild一下
参考
- 单向数据流和双向数据流