做爲系列文章的第十二篇,本篇將經過 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、全面深刻理解狀態管理設計)》