大概是四月份左右,裸辞了一波。以后就一直在打游戏、复习、面试中循环度日,到如今尚未一个特别满意的结果。git
感受本身开始往佛系的方向发展了,难道这就是大起大落后的大彻大悟吗?github
上面的话就权当开个玩笑,本篇文章的原由是在某次面试中,一位面试官问我Flutter里跨组件通讯有哪些方式,我说的其中一种就是作一个统一管理,这样全局获取后就能够跨组件通讯了,不过面试官没有给到一个正面的反馈,因此我就打算作一个这样的状态管理组件出来。若是下次再有人问我这个问题,我就会告诉他——“我给你讲讲我写的一个组件吧(微笑)”面试
下面开始正题bash
想要作一个状态管理的组件,首先得了解一下Flutter的刷新流程,在前面写的 《从源码看Flutter系列》, 已经对这一过程有所了解,下面再简单介绍一下markdown
setState()
后,将对应的 Element
添加到 BuildOwner
维护的 _dirtyElements
列表中engine
的 frame
回调通知,会触发 WidgetsBinding
的 drawFrame()
方法,而后会遍历以前的 _dirtyElements
,根据 Element
在树中的高度,由上到下调用其 rebuild()
方法进行从新建立或更新Element
的刷新过程当中,会将须要从新layout、paint的 RenderObject
存放在 PipelineOwner
维护的各个列表里,以后会在 RendererBinding
的 drawFrame()
方法里对 RenderObject
来一个统一的更新BuildOwner
的 finalizeTree()
来进行统一的销毁操做了以上就是刷新流程的一个大体介绍。经过这个流程咱们知道,对于须要更新或者销毁的对象,Flutter的作法就是放入一个列表中进行统一操做,在了解到这个事实后,显然组件状态也是能够统一管理的,这也就是后面将要实现的状态管理组件的核心原理啦。数据结构
在正式介绍状态管理组件以前,我仍是要先介绍一下 InheritedElement
这个常见嘉宾,Flutter中的全局主题修改等都是基于这个对象的,它对应的 Widget
是 InheritedWidget
,经过使用 InheritedWidget
,咱们也能够作到跨组件通讯。不过我我的总以为它的使用方式不太美观,因此几乎不多用到。ide
如今很是受欢迎的 provider
库与以前的 scope_model
,都是基于 InheritedElement
来实现的,可是在使用 provider
的过程当中会遇到这样一个问题:post
当你在 PageC
经过 Provider.of<ModelB>(context)
来获取 PageB
对应的 Model
时,是会报错的,由于获取到的对象为null。ui
致使报错其实涉及到两个缘由,分别与 InheritedElement
和页面栈相关,下面就来简单的说明一下。this
provider中经常使用 Provider.of<T>(context)
来获取对应的数据对象,最终调用的都是 BuildContext
中的 getElementForInheritedWidgetOfExactType
方法,它的实现以下
///Element
Map<Type, InheritedElement> _inheritedWidgets;
///InheritedElement
@override
InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
...
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
return ancestor;
}
复制代码
查找是经过而 _inheritedWidgets
来进行的,而它在 InheritedElement
中是如何传递的呢?
///InheritedElement
@override
void _updateInheritance() {
assert(_active);
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;
}
复制代码
就是经过 copy 父节点的 _inheritedWidgets
来达到传递效果,这在以前的《从源码看Element》中就已经提到过
到这里就知道了 InheritedElement
是如何传递和查找的了,接下来咱们看一下致使 provider
没法获取对象的另一个缘由
咱们打开和弹出一个页面,都是经过 Navigator
来操做的,而最终全部的页面都会被封装到 OverlayEntryWidget
中,被添加到 _Theatre
所持有的 children
列表里,也就是说全部的页面在数据结构上实际是平级的关系,下面用一个简单的图形表示一下
由于 InheritedElement
的查找就是经过父节点向上遍历,直到找到指定的对象为止,不然返回null,而这里因为 PageC 与 PageB 是平级的关系,显然 PageC 没法找到 PageB 对应的数据(其实是对应的Element为平级,这里作了简化)
这也就是使用 provider
会遇到这样问题的缘由,固然解决办法也很简单,就是将 Model
都放入 GlobalModel
中,经过 GlobalModel
获取便可
上面介绍完的这些对于理解状态管理有必定的帮助,下面就开始正式介绍我是如何实现状态管理组件的
实现的思路很是简单,就是经过维护一个 HashMap
对象,将各个页面对应的 Model
放入其中,获取的时候经过这个 HashMap
获取便可。
不过可能会遇到下面这种场景:
当须要push多个相同的页面时,会有多个同类型的 Model
对象,显然这在 HashMap
中是没法经过类型来获取指定 Model
的,解决办法也很简单,那就是再维护一个 HashMap
,而 key
由使用者指定,这样就没必要担忧冲突的问题了
原理大体就是这样,最终代码以下
class ModelWidget<T extends Model> extends StatefulWidget {
final ChildBuilder<T> childBuilder;
final ModelBuilder<T> modelBuilder;
final String modelKey;
const ModelWidget(
{Key key,
@required this.childBuilder,
@required this.modelBuilder,
this.modelKey})
: super(key: key);
@override
_ModelWidgetState createState() => _ModelWidgetState<T>();
}
typedef ChildBuilder<T extends Model> = Widget Function(
BuildContext context, T model);
typedef ModelBuilder<T extends Model> = T Function();
class _ModelWidgetState<T extends Model> extends State<ModelWidget<T>> {
...
}
class Model { ... }
class _StateDelegate { ... }
class ModelGroup {
static Map<Type, Model> _map = new HashMap();
static Map<String, Model> _repeatMap = new HashMap();
static void _pushModel(Model model) => _map[model.runtimeType] = model;
static void _pushModelWithKey(String key, Model model) =>
_repeatMap[key] = model;
static void _popModel(Model model) => _map.remove(model.runtimeType);
static void _popModelWithKey(String key, Model model) => _repeatMap.remove(key);
static T findModel<T extends Model>() => _map[T];
static T findModelByKey<T extends Model>(String key) => _repeatMap[key];
}
复制代码
因为总共的代码量很是少,对细节有兴趣的小伙伴能够直接去看源码
使用方式以下
首先定义你的 Model
对象
class YourModel extends Model {
@override
void initState() {...}
@override
void dispose() {...}
int value = 0;
}
复制代码
当你想要把它与某个Widget或页面结合使用时,能够像下面这样
ModelWidget<YourModel>(
childBuilder: (ctx, model) => YourWidgetOrPage(),
modelBuilder: () => YourModel(),
),
复制代码
获取数据
final model = ModelGroup.findModel<YourModel>();
复制代码
刷新
model.refresh();
复制代码
你也能够直接尝试一下这个在线demo,点击体验
裸辞期间总共开源了两个组件:
同时,最后声明一下:落魄小哥,在线求职
有好的内推机会请务必不要放过我,个人联系方式就在上面的博客地址中