做为系列文章的第十二篇,本篇将经过 scope_model 、 BloC 设计模式、flutter_redux 、 fish_redux 来全面深刻分析, Flutter 中你们最为关心的状态管理机制,理解各大框架中如何设计实现状态管理,从而选出你最为合适的 state “大管家”。前端
前文:git
在全部 响应式编程 中,状态管理一直老生常谈的话题,而在 Flutter 中,目前主流的有 scope_model
、BloC 设计模式
、flutter_redux
、fish_redux
等四种设计,它们的 复杂度 和 上手难度 是逐步递增的,但同时 可拓展性 、解耦度 和 复用能力 也逐步提高。github
基于前篇,咱们对 Stream
已经有了全面深刻的理解,后面能够发现这四大框架或多或少都有 Stream
的应用,不过仍是那句老话,合适才是最重要,不要为了设计而设计 。编程
本文Demo源码redux
scoped_model
是 Flutter 最为简单的状态管理框架,它充分利用了 Flutter 中的一些特性,只有一个 dart 文件的它,极简的实现了通常场景下的状态管理。数组
以下方代码所示,利用 scoped_model
实现状态管理只须要三步 :bash
Model
的实现,如 CountModel
,而且在状态改变时执行 notifyListeners()
方法。ScopedModel
Widget 加载 Model
。ScopedModelDescendant
或者 ScopedModel.of<CountModel>(context)
加载 Model
内状态数据。是否是很简单?那仅仅一个 dart 文件,如何实现这样的效果的呢?后面咱们立刻开始剥析它。app
class ScopedPage extends StatelessWidget {
final CountModel _model = new CountModel();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: new Text("scoped"),
),
body: Container(
child: new ScopedModel<CountModel>(
model: _model,
child: CountWidget(),
),
));
}
}
class CountWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new ScopedModelDescendant<CountModel>(
builder: (context, child, model) {
return new Column(
children: <Widget>[
new Expanded(child: new Center(child: new Text(model.count.toString()))),
new Center(
child: new FlatButton(
onPressed: () {
model.add();
},
color: Colors.blue,
child: new Text("+")),
),
],
);
});
}
}
class CountModel extends Model {
static CountModel of(BuildContext context) =>
ScopedModel.of<CountModel>(context);
int _count = 0;
int get count => _count;
void add() {
_count++;
notifyListeners();
}
}
复制代码
以下图所示,在 scoped_model
的整个实现流程中,ScopedModel
这个 Widget 很巧妙的借助了 AnimatedBuildler
。框架
由于 AnimatedBuildler
继承了 AnimatedWidget
,在 AnimatedWidget
的生命周期中会对 Listenable
接口添加监听,而 Model
刚好就实现了 Listenable
接口,整个流程总结起来就是:
Model
实现了 Listenable
接口,内部维护一个 Set<VoidCallback> _listeners
。Model
设置给 AnimatedBuildler
时, Listenable
的 addListener
会被调用,而后添加一个 _handleChange
监听到 _listeners
这个 Set 中。Model
调用 notifyListeners
时,会经过异步方法 scheduleMicrotask
去从头至尾执行一遍 _listeners
中的 _handleChange
。_handleChange
监听被调用,执行了 setState({})
。整个流程是否是很巧妙,机制的利用了 AnimatedWidget
和 Listenable
在 Flutter 中的特性组合,至于 ScopedModelDescendant
就只是为了跨 Widget 共享 Model
而作的一层封装,主要仍是经过 ScopedModel.of<CountModel>(context)
获取到对应 Model 对象,这这个实现上,scoped_model
依旧利用了 Flutter 的特性控件 InheritedWidget
实现。
在 scoped_model
中咱们能够经过 ScopedModel.of<CountModel>(context)
获取咱们的 Model ,其中最主要是由于其内部的 build 的时候,包裹了一个 _InheritedModel
控件,而它继承了 InheritedWidget
。
为何咱们能够经过 context
去获取到共享的 Model
对象呢?
首先咱们知道 context
只是接口,而在 Flutter 中 context
的实现是 Element
,在 Element
的 inheritFromWidgetOfExactType
方法实现里,有一个 Map<Type, InheritedElement> _inheritedWidgets
的对象。
_inheritedWidgets
通常状况下是空的,只有当父控件是 InheritedWidget
或者自己是 InheritedWidgets
时才会有被初始化,而当父控件是 InheritedWidget
时,这个 Map 会被一级一级往下传递与合并 。
因此当咱们经过 context
调用 inheritFromWidgetOfExactType
时,就能够往上查找到父控件的 Widget,从在 scoped_model
获取到 _InheritedModel
中的Model
。
BloC
全称 Business Logic Component ,它属于一种设计模式,在 Flutter 中它主要是经过 Stream
与 SteamBuilder
来实现设计的,因此 BloC
实现起来也相对简单,关于 Stream
与 SteamBuilder
的实现原理能够查看前篇,这里主要展现如何完成一个简单的 BloC
。
以下代码所示,整个流程总结为:
PageBloc
对象,利用 StreamController
建立 Sink
与 Stream
。PageBloc
对外暴露 Stream
用来与 StreamBuilder
结合;暴露 add 方法提供外部调用,内部经过 Sink
更新 Stream
。StreamBuilder
加载监听 Stream
数据流,经过 snapShot 中的 data 更新控件。固然,若是和 rxdart
结合能够简化 StreamController
的一些操做,同时若是你须要利用 BloC
模式实现状态共享,那么本身也能够封装多一层 InheritedWidgets
的嵌套,若是对于这一块有疑惑的话,推荐能够去看看上一篇的 Stream 解析。
class _BlocPageState extends State<BlocPage> {
final PageBloc _pageBloc = new PageBloc();
@override
void dispose() {
_pageBloc.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: new StreamBuilder(
initialData: 0,
stream: _pageBloc.stream,
builder: (context, snapShot) {
return new Column(
children: <Widget>[
new Expanded(
child: new Center(
child: new Text(snapShot.data.toString()))),
new Center(
child: new FlatButton(
onPressed: () {
_pageBloc.add();
},
color: Colors.blue,
child: new Text("+")),
),
new SizedBox(
height: 100,
)
],
);
}),
),
);
}
}
class PageBloc {
int _count = 0;
///StreamController
StreamController<int> _countController = StreamController<int>();
///对外提供入口
StreamSink<int> get _countSink => _countController.sink;
///提供 stream StreamBuilder 订阅
Stream<int> get stream => _countController.stream;
void dispose() {
_countController.close();
}
void add() {
_count++;
_countSink.add(_count);
}
}
复制代码
相信若是是前端开发者,对于 redux
模式并不会陌生,而 flutter_redux
能够看作是利用了 Stream
特性的 scope_model
升级版,经过 redux
设计模式来完成解耦和拓展。
固然,更多的功能和更好的拓展性,也形成了代码的复杂度和上手难度 ,由于 flutter_redux
的代码使用篇幅问题,这里就不展现全部代码了,须要看使用代码的可直接从 demo 获取,如今咱们直接看 flutter_redux
是如何实现状态管理的吧。
如上图,咱们知道 redux
中通常有 Store
、 Action
、 Reducer
三个主要对象,以外还有 Middleware
中间件用于拦截,因此以下代码所示:
Store
用于管理状态 。Store
增长 appReducer
合集方法,增长须要拦截的 middleware
,并初始化状态。Store
设置给 StoreProvider
这个 InheritedWidget
。StoreConnector
/ StoreBuilder
加载显示 Store
中的数据。以后咱们能够 dispatch
一个 Action ,在通过 middleware
以后,触发对应的 Reducer 返回数据,而事实上这里核心的内容实现,仍是 Stream
和 StreamBuilder
的结合使用 ,接下来就让咱们看看这个流程是如何联动起来的吧。
class _ReduxPageState extends State<ReduxPage> {
///初始化store
final store = new Store<CountState>(
/// reducer 合集方法
appReducer,
///中间键
middleware: middleware,
///初始化状态
initialState: new CountState(count: 0),
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: new Text("redux"),
),
body: Container(
/// StoreProvider InheritedWidget
/// 加载 store 共享
child: new StoreProvider(
store: store,
child: CountWidget(),
),
));
}
}
复制代码
以下图所示,是 flutter_redux
从入口到更新的完整流程图,整理这个流程其中最关键有几个点是:
StoreProvider
是 InheritedWidgets
,因此它能够经过 context
实现状态共享。StreamBuilder
/ StoreConnector
的内部实现主要是 StreamBuilder
。Store
内部是经过 StreamController.broadcast
建立的 Stream
,而后在 StoreConnector
中经过 Stream
的 map
、transform
实现小状态的变换,最后更新到 StreamBuilder
。那么如今看下图流程有点晕?下面咱们直接分析图中流程。
能够看出整个流程的核心仍是 Stream
,基于这几个关键点,咱们把上图的流程整理为:
Store
建立时传入 reducer
对象和 middleware
数组,同时经过 StreamController.broadcast
建立了 _changeController
对象。Store
利用 middleware
和 _changeController
组成了一个 NextDispatcher
方法数组 ,并把 _changeController
所在的 NextDispatcher
方法放置在数组最后位置。StoreConnector
内经过 Store
的 _changeController
获取 Stream
,并进行了一系列变换后,最终 Stream
设置给了 StreamBuilder
。Stroe
的 dispatch
方法时,咱们会先进过 NextDispatcher
数组中的一系列 middleware
拦截器,最终调用到队末的 _changeController
所在的 NextDispatcher
。NextDispatcher
执行时会先执行 reducer
方法获取新的 state
,而后经过 _changeController.add
将状态加载到 Stream
流程中,触发 StoreConnector
的 StreamBuilder
更新数据。若是对于
Stream
流程不熟悉的还请看上篇。
如今再对照流程图会不会清晰不少了?
在 flutter_redux
中,开发者的每一个操做都只是一个 Action
,而这个行为所触发的逻辑彻底由 middleware
和 reducer
决定,这样的设计在必定程度上将业务与UI隔离,同时也统一了状态的管理。
好比你一个点击行为只是发出一个
RefrshAction
,可是经过middleware
拦截以后,在其中异步处理完几个数据接口,而后从新dispatch
出Action1
、Action2
、Action3
去更新其余页面, 相似的redux_epics
库就是这样实现异步的middleware
逻辑。
若是说 flutter_redux
属于相对复杂的状态管理设置的话,那么闲鱼开源的 fish_redux
可谓 “不走寻常路” 了,虽然是基于 redux
原有的设计理念,同时也有使用到 Stream
,可是相比较起来整个设计彻底是 超脱三界,若是是前面的都是简单的拼积木,那是 fish_redux
就是积木界的乐高。
由于篇幅缘由,这里也只展现部分代码,其中 reducer
仍是咱们熟悉的存在,而闲鱼在这 redux
的基础上提出了 Comoponent
的概念,这个概念下 fish_redux
是从 Context
、Widget
等地方就开始全面“入侵”你的代码,从而带来“超级赛亚人”版的 redux
。
以下代码所示,默认状况咱们须要:
Page
实现咱们的页面。State
状态。effect
、 middleware
、reducer
用于实现反作用、中间件、结果返回处理。view
用于绘制页面。dependencies
用户装配控件,这里最骚气的莫过于重载了 + 操做符,而后利用 Connector
从 State
挑选出数据,而后经过 Component
绘制。如今看起来使用流程是否是变得复杂了?
可是这带来的好处就是 复用的颗粒度更细了,装配和功能更加的清晰。 那这个过程是如何实现的呢?后面咱们将分析这个复杂的流程。
class FishPage extends Page<CountState, Map<String, dynamic>> {
FishPage()
: super(
initState: initState,
effect: buildEffect(),
reducer: buildReducer(),
///配置 View 显示
view: buildView,
///配置 Dependencies 显示
dependencies: Dependencies<CountState>(
slots: <String, Dependent<CountState>>{
///经过 Connector() 从 大 state 转化处小 state
///而后将数据渲染到 Component
'count-double': DoubleCountConnector() + DoubleCountComponent()
}
),
middleware: <Middleware<CountState>>[
///中间键打印log
logMiddleware(tag: 'FishPage'),
]
);
}
///渲染主页
Widget buildView(CountState state, Dispatch dispatch, ViewService viewService) {
return Scaffold(
appBar: AppBar(
title: new Text("fish"),
),
body: new Column(
children: <Widget>[
///viewService 渲染 dependencies
viewService.buildComponent('count-double'),
new Expanded(child: new Center(child: new Text(state.count.toString()))),
new Center(
child: new FlatButton(
onPressed: () {
///+
dispatch(CountActionCreator.onAddAction());
},
color: Colors.blue,
child: new Text("+")),
),
new SizedBox(
height: 100,
)
],
));
}
复制代码
以下大图所示,整个联动的流程比 flutter_redux
复杂了更多( 若是看不清能够点击大图 ),而这个过程咱们总结起来就是:
一、Page
的构建须要 State
、Effect
、Reducer
、view
、dependencies
、 middleware
等参数。
二、Page
的内部 PageProvider
是一个 InheritedWidget
用户状态共享。
三、Page
内部会经过 createMixedStore
建立 Store
对象。
四、Store
对象对外提供的 subscribe
方法,在订阅时会将订阅的方法添加到内部 List<_VoidCallback> _listeners
。
五、Store
对象内部的 StreamController.broadcast
建立出了 _notifyController
对象用于广播更新。
六、Store
对象内部的 subscribe
方法,会在 ComponentState
中添加订阅方法 onNotify
,若是调用在 onNotify
中最终会执行 setState
更新UI。
七、Store
对象对外提供的 dispatch
方法,执行时内部会执行 4 中的 List<_VoidCallback> _listeners
,触发 onNotify
。
八、Page
内部会经过 Logic
建立 Dispatch
,执行时经历 Effect
-> Middleware
-> Stroe.dispatch
-> Reducer
-> State
-> _notifyController
-> _notifyController.add(state)
等流程。
九、以上流程最终就是 Dispatch
触发 Store
内部 _notifyController
, 最终会触发 ComponentState
中的 onNotify
中的setState
更新UI
是否是有不少对象很陌生?
确实 fish_redux
的总体流程更加复杂,内部的 ContxtSys
、Componet
、ViewSerivce
、 Logic
等等概念设计,这里由于篇幅有限就不详细拆分展现了,但从整个流程能够看出 fish_redux
从控件到页面更新,全都进行了新的独立设计,而这里面最有意思的,莫不过 dependencies
。
以下图所示,得益于fish_redux
内部 ConnOpMixin
中对操做符的重载,咱们能够经过 DoubleCountConnector() + DoubleCountComponent()
来实现Dependent
的组装。
Dependent
的组装中 Connector
会从总 State 中读取须要的小 State 用于 Component
的绘制,这样很好的达到了 模块解耦与复用 的效果。
而使用中咱们组装的 dependencies
最后都会经过 ViewService
提供调用调用能力,好比调用 buildAdapter
用于列表能力,调用 buildComponent
提供独立控件能力等。
能够看出 flutter_redux
的内部实现复杂度是比较高的,在提供组装、复用、解耦的同时,也对项目进行了必定程度的入侵,这里的篇幅可能不能很全面的分析 flutter_redux
中的整个流程,可是也能让你理解整个流程的关键点,细细品味设计之美。
自此,第十二篇终于结束了!(///▽///)
《Flutter完整开发实战详解(1、Dart语言和Flutter基础)》
《Flutter完整开发实战详解(4、Redux、主题、国际化)》
《Flutter完整开发实战详解(6、 深刻Widget原理)》
《Flutter完整开发实战详解(10、 深刻图片加载流程)》
《Flutter完整开发实战详解(11、全面深刻理解Stream)》
《Flutter完整开发实战详解(12、全面深刻理解状态管理设计)》