前言
Flutter的很多靈感來自于React,它的設(shè)計(jì)思想是數(shù)據(jù)與視圖分離,由數(shù)據(jù)映射渲染視圖。所以在Flutter中,它的Widget是immutable的,而它的動態(tài)部分全部放到了狀態(tài)(State)中。于是狀態(tài)管理自然便成了我們密切關(guān)注的對象。
在之前我們已經(jīng)討論了關(guān)于在flutter中使用scoped_model進(jìn)行狀態(tài)管理的應(yīng)用。文章發(fā)出后,有許多同學(xué)都在問我,到底redux和scoped到底誰更好。
這個系列將會從這幾個狀態(tài)管理方案進(jìn)行深入研究:
- Scoped_model
- redux
- BLoC
- 對比總結(jié)篇
所以今天要和大家介紹的是在flutter中使用Redux進(jìn)行狀態(tài)管理。
我希望各位在閱讀這篇文章之前,先仔細(xì)思考以下這幾個問題。
- 什么是redux
- redux給我們了什么好處,我們?yōu)槭裁匆褂盟?/li>
- 它的基本思想是什么
- redux是否真的適合我們
ok,我們開始正式的介紹redux。
Redux
為什么需要狀態(tài)管理
在我們一開始構(gòu)建應(yīng)用的時候,也許很簡單。我們有一些狀態(tài),直接把他們映射成視圖就可以了。這種簡單應(yīng)用可能并不需要狀態(tài)管理。
但是隨著功能的增加,你的應(yīng)用程序?qū)袔资畟€甚至上百個狀態(tài)。這個時候你的應(yīng)用應(yīng)該會是這樣。
Wow,這是什么鬼。我們很難再清楚的測試維護(hù)我們的狀態(tài),因?yàn)樗瓷先?shí)在是太復(fù)雜了!而且還會有多個頁面共享同一個狀態(tài),例如當(dāng)你進(jìn)入一個文章點(diǎn)贊,退出到外部縮略展示的時候,外部也需要顯示點(diǎn)贊數(shù),這時候就需要同步這兩個狀態(tài)。
這時候,我們便迫切的需要一個架構(gòu)來幫助我們理清這些關(guān)系,狀態(tài)管理框架應(yīng)運(yùn)而生。
redux是什么
Redux是一種單向數(shù)據(jù)流架構(gòu),可以輕松開發(fā),維護(hù)和測試應(yīng)用程序。
- 我們在Redux中,所有的狀態(tài)都儲存在Store里。這個Store會放在App頂層。
- View拿到Store儲存的狀態(tài)(State)并把它映射成視圖。View還會與用戶進(jìn)行交互,用戶點(diǎn)擊按鈕滑動屏幕等等,這時會因?yàn)榻换バ枰獢?shù)據(jù)發(fā)生改變。
- Redux讓我們不能讓View直接操作數(shù)據(jù),而是通過發(fā)起一個action來告訴Reducer,狀態(tài)得改變啦。
- 這時候Reducer接收到了這個action,他就回去遍歷action表,然后找到那個匹配的action,根據(jù)action生成新的狀態(tài)并把新的狀態(tài)放到Store中。
- Store丟棄了老的狀態(tài)對象,儲存了新的狀態(tài)對象后,就通知所有使用到了這個狀態(tài)的View更新(類似setState)。這樣我們就能夠同步不同view中的狀態(tài)了。
Lets do it!
這里我們以一個最簡單的CountApp舉例。簡單介紹flutter_redux/redux的用法。該項(xiàng)目完整代碼已上傳Github。
這是一個在不同頁面使用Redux共享狀態(tài)信息的app。這兩個頁面都依賴于一個數(shù)字,這個數(shù)字會隨著我們按下按鈕的次數(shù)而增加。
第一步:添加依賴
我們這里使用了redux/flutter_redux庫,它們都是由Brian Egan大神編寫的。其中flutter_redux是用來簡化redux的使用的。
- 實(shí)際添加請參考:https://pub.dartlang.org/packages/scoped_model#-installing-tab-
- 由于版本沖突添加失敗請參考:https://juejin.im/post/5b8958d351882542b03e6d57
第二步:創(chuàng)建State
我們剛才介紹了Redux的流程,狀態(tài)是由reducer生成并儲存在Store里面的。Store更新狀態(tài)的時候,并不是更改原來的狀態(tài)對象,而是直接將reducer生成的新的狀態(tài)對象替換掉老的狀態(tài)對象。所以,我們的狀態(tài)應(yīng)該是immutable的。
import 'package:meta/meta.dart';
/**
* State中所有屬性都應(yīng)該是只讀的
*/
@immutable
class CountState{
int _count;
get count => _count;
CountState(this._count);
}
第三步:創(chuàng)建action
可能各位最開始接觸的時候?qū)ction還會摸不著頭腦。action到底是什么?View如何發(fā)出action。其實(shí),action只是我們對狀態(tài)進(jìn)行操作方法的一個代號而已。在我們這個應(yīng)用中,唯一的一個功能就是讓count的值+1,所以我們這里只有一個action。
/**
* 定義操作該State的全部Action
* 這里只有增加count一個動作
*/
enum Action{
increment
}
第四步:創(chuàng)建reducer
reducer是我們的狀態(tài)生成器,它接收一個我們原來的狀態(tài),然后接收一個action,再匹配這個action生成一個新的狀態(tài)。
/**
* reducer會根據(jù)傳進(jìn)來的action生成新的CountState
*/
CountState reducer(CountState state,action){
//匹配Action
if(action == Action.increment){
return CountState(state.count+1);
}
return state;
}
第五步:創(chuàng)建store
Store接收一個reducer,以及初始化State,我們想用Redux管理全局的狀態(tài)的話,需要將store儲存在應(yīng)用的入口才行。而在應(yīng)用打開時要先初始化一次應(yīng)用的狀態(tài)。所以在State中添加一個初始化的函數(shù)。
//這段代碼寫在State中
CountState.initState(){ _count = 0;}
//應(yīng)用頂層
void main() {
final store =
Store<CountState>(reducer, initialState: CountState.initState());
runApp(new MyApp(store));
}
第六步:將Store放入頂層
flutter_redux提供了一個很棒的widget叫做StoreProvider,它的用法也很簡單,接收一個store,和child Widget。
class MyApp extends StatelessWidget {
final Store<CountState> store;
MyApp(this.store);
@override
Widget build(BuildContext context) {
return StoreProvider<CountState>(
store: store,
child: new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: TopScreen(),
),
);
}
}
第六步:在子頁面中獲取Store中的state
這里建議大家把實(shí)際代碼對照下面的解釋一起看。
StoreConnector<CountState,int>(
converter: (store) => store.state.count,
builder: (context, count) {
return Text(
count.toString(),
style: Theme.of(context).textTheme.display1,
);
},
),
要想獲取store我們需要使用StoreConnector<S,ViewModel>。StoreConnector能夠通過StoreProvider找到頂層的store。而且能夠在state發(fā)生變化時rebuilt Widget。
- 首先這里需要強(qiáng)制聲明類型,S代表我們需要從store中獲取什么類型的state,ViewModel指的是我們使用這個State時的實(shí)際類型。
- 然后我們需要聲明一個converter<S,ViewModel>,它的作用是將Store轉(zhuǎn)化成實(shí)際ViewModel將要使用的信息,比如我們這里實(shí)際上要使用的是count,所以這里將count提取出來。
- builder是我們實(shí)際根據(jù)state創(chuàng)建Widget的地方,它接收一個上下文context,以及剛才我們轉(zhuǎn)化出來的ViewModel,所以我們就只需要把拿到的count放進(jìn)Text Widget中進(jìn)行渲染就好了。
第七步:發(fā)出action
我們這個應(yīng)用在第二個頁面中,通過點(diǎn)擊floatingActionButton發(fā)出了action,并通知reducer生成了新的狀態(tài)。
floatingActionButton: StoreConnector<CountState,VoidCallback>(
converter: (store) {
return () => store.dispatch(Action.increment);
},
builder: (context, callback) {
return FloatingActionButton(
onPressed: callback,
child: Icon(Icons.add),
);
},
),
- 同樣,我們還是使用StoreConnector<S,ViewModel>。這里由于是發(fā)出了一個動作,所以是VoidCallback。
- store.dispatch發(fā)起一個action,任何中間件都會攔截該操作,在運(yùn)行中間件后,操作將被發(fā)送到給定的reducer生成新的狀態(tài),并更新狀態(tài)樹。
以上便是在flutter中使用redux共享狀態(tài)信息的全部內(nèi)容。
Q&A
ViewModel性能優(yōu)化
我們的StoreConnector能夠?qū)tore提取出信息并轉(zhuǎn)化成ViewModel,這里其實(shí)是有一個性能優(yōu)化的點(diǎn)的。我們這里的例子非常簡單,它的ViewModel就只是一個int的值,當(dāng)我們ViewModel很復(fù)雜的時候,我們可以使用StoreConnector的distinct屬性進(jìn)行性能優(yōu)化。使用方法很簡單:需要我們在ViewModel中重寫[==] and [hashCode] 方法,然后把distinct屬性設(shè)為true。
如何處理異步數(shù)據(jù)
Redux提供了一種簡單的方法來更新應(yīng)用程序的狀態(tài)以響應(yīng)同步操作。但是,它缺少處理異步代碼的工具。我們?nèi)绾螒?yīng)對異步相應(yīng)呢。
這里就需要一個interrupt來處理異步請求,然后再發(fā)出新的action通知reducer生成新的State了。
這里有brianegan大神寫的另外一個幫助在flutter中使用redux處理異步請求的庫redux_thunk。我會在之后的文章中詳細(xì)介紹如何在redux中處理異步操作。
你認(rèn)為redux真的適合flutter嗎
我們發(fā)現(xiàn),redux的確能夠在flutter中很好的工作。在react中數(shù)據(jù)是沒有上行能力的,所以通過數(shù)據(jù)單向流動形成一個環(huán)來進(jìn)行狀態(tài)管理??瓷先ニ坪醪]有把flutter中的優(yōu)勢完全發(fā)揮出來。在這個簡單的例子中我們也可以看出,使用redux還是稍微有些麻煩的,用的不好,可能會陷入redux地獄。學(xué)習(xí)成本偏高也是它的一大痛點(diǎn)。
當(dāng)然,redux這套狀態(tài)管理架構(gòu)已經(jīng)比較成熟,假如您已經(jīng)習(xí)慣redux,也能夠快速通過flutter_redux輕松構(gòu)建屬于您的狀態(tài)管理應(yīng)用。
那么你現(xiàn)在如何看待redux呢?
寫在最后
本次所用到的代碼已經(jīng)上傳Github:
https://github.com/Vadaski/Flutter-Notebook/tree/master/mecury_project/example/redux_demo
這篇文章參考了以下資料
了解更多
你能在這些地方了解更多關(guān)于flutter-redux
- 了解flutter-redux:https://flutterbyexample.com/what-is-redux/
- 了解redux的問題:https://medium.com/fluttery/the-flutter-redux-problem-fa9d59ec97b8
如果您對flutter_redux還有任何看法或者文章的建議或者文章中有任何不對之處,歡迎在下方評論區(qū)以及我的郵箱1652219550a@gmail.com留言,我會在24小時內(nèi)與您聯(lián)系!
按理說下一章我們將探索BLoC在Flutter中的實(shí)踐,而BLoC非常Reactive Programming,所以我決定先讓大家了解一些dart:Stream的知識再介紹它,所以下一篇文章我們會介紹Stream以及流式編程,敬請關(guān)注。