前言
Flutter的很多靈感來自於React,它的設計思想是資料與檢視分離,由資料對映渲染檢視。所以在Flutter中,它的Widget是immutable的,而它的動態部分全部放到了狀態(State)中。於是狀態管理自然便成了我們密切關注的物件。
在之前我們已經討論了關於在flutter中使用scoped_model進行狀態管理的應用。文章發出後,有許多同學都在問我,到底redux和scoped到底誰更好。
這個系列將會從這幾個狀態管理方案進行深入研究:
- Scoped_model
- redux
- BLoC
- 對比總結篇
所以今天要和大家介紹的是在flutter中使用Redux進行狀態管理。 我希望各位在閱讀這篇文章之前,先仔細思考以下這幾個問題。
- 什麼是redux
- redux給我們了什麼好處,我們為什麼要使用它
- 它的基本思想是什麼
- redux是否真的適合我們
ok,我們開始正式的介紹redux。
Redux
為什麼需要狀態管理
在我們一開始構建應用的時候,也許很簡單。我們有一些狀態,直接把他們對映成檢視就可以了。這種簡單應用可能並不需要狀態管理。
但是隨著功能的增加,你的應用程式將會有幾十個甚至上百個狀態。這個時候你的應用應該會是這樣。
Wow,這是什麼鬼。我們很難再清楚的測試維護我們的狀態,因為它看上去實在是太複雜了!而且還會有多個頁面共享同一個狀態,例如當你進入一個文章點贊,退出到外部縮略展示的時候,外部也需要顯示點贊數,這時候就需要同步這兩個狀態。這時候,我們便迫切的需要一個架構來幫助我們理清這些關係,狀態管理框架應運而生。
redux是什麼
Redux是一種單向資料流架構,可以輕鬆開發,維護和測試應用程式。
- 我們在Redux中,所有的狀態都儲存在Store裡。這個Store會放在App頂層。
- View拿到Store儲存的狀態(State)並把它對映成檢視。View還會與使用者進行互動,使用者點選按鈕滑動螢幕等等,這時會因為互動需要資料發生改變。
- Redux讓我們不能讓View直接運算元據,而是通過發起一個action來告訴Reducer,狀態得改變啦。
- 這時候Reducer接收到了這個action,他就回去遍歷action表,然後找到那個匹配的action,根據action生成新的狀態並把新的狀態放到Store中。
- Store丟棄了老的狀態物件,儲存了新的狀態物件後,就通知所有使用到了這個狀態的View更新(類似setState)。這樣我們就能夠同步不同view中的狀態了。
Lets do it!
這裡我們以一個最簡單的CountApp舉例。簡單介紹flutter_redux/redux的用法。該專案完整程式碼已上傳Github。
這是一個在不同頁面使用Redux共享狀態資訊的app。這兩個頁面都依賴於一個數字,這個數字會隨著我們按下按鈕的次數而增加。
第一步:新增依賴
我們這裡使用了redux/flutter_redux庫,它們都是由Brian Egan大神編寫的。其中flutter_redux是用來簡化redux的使用的。- 實際新增請參考:redux,flutter_redux
- 由於版本衝突新增失敗請參考:juejin.im/post/5b8958…
第二步:建立State
我們剛才介紹了Redux的流程,狀態是由reducer生成並儲存在Store裡面的。Store更新狀態的時候,並不是更改原來的狀態物件,而是直接將reducer生成的新的狀態物件替換掉老的狀態物件。所以,我們的狀態應該是immutable的。
import 'package:meta/meta.dart';
/**
* State中所有屬性都應該是隻讀的
*/
@immutable
class CountState{
int _count;
get count => _count;
CountState(this._count);
}
複製程式碼
第三步:建立action
可能各位最開始接觸的時候對Action還會摸不著頭腦。action到底是什麼?View如何發出action。其實,action只是我們對狀態進行操作方法的一個代號而已。在我們這個應用中,唯一的一個功能就是讓count的值+1,所以我們這裡只有一個action。
/**
* 定義操作該State的全部Action
* 這裡只有增加count一個動作
*/
enum Action{
increment
}
複製程式碼
第四步:建立reducer
reducer是我們的狀態生成器,它接收一個我們原來的狀態,然後接收一個action,再匹配這個action生成一個新的狀態。
/**
* reducer會根據傳進來的action生成新的CountState
*/
CountState reducer(CountState state,action){
//匹配Action
if(action == Action.increment){
return CountState(state.count+1);
}
return state;
}
複製程式碼
第五步:建立store
Store接收一個reducer,以及初始化State,我們想用Redux管理全域性的狀態的話,需要將store儲存在應用的入口才行。而在應用開啟時要先初始化一次應用的狀態。所以在State中新增一個初始化的函式。
//這段程式碼寫在State中
CountState.initState(){ _count = 0;}
複製程式碼
//應用頂層
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
這裡建議大家把實際程式碼對照下面的解釋一起看。
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發生變化時rebuilt Widget。
- 首先這裡需要強制宣告型別,S代表我們需要從store中獲取什麼型別的state,ViewModel指的是我們使用這個State時的實際型別。
- 然後我們需要宣告一個converter<S,ViewModel>,它的作用是將Store轉化成實際ViewModel將要使用的資訊,比如我們這裡實際上要使用的是count,所以這裡將count提取出來。
- builder是我們實際根據state建立Widget的地方,它接收一個上下文context,以及剛才我們轉化出來的ViewModel,所以我們就只需要把拿到的count放進Text Widget中進行渲染就好了。
第七步:發出action
我們這個應用在第二個頁面中,通過點選floatingActionButton發出了action,並通知reducer生成了新的狀態。
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>。這裡由於是發出了一個動作,所以是VoidCallback。
- store.dispatch發起一個action,任何中介軟體都會攔截該操作,在執行中介軟體後,操作將被髮送到給定的reducer生成新的狀態,並更新狀態樹。
以上便是在flutter中使用redux共享狀態資訊的全部內容。
Q&A
ViewModel效能優化
我們的StoreConnector能夠將store提取出資訊並轉化成ViewModel,這裡其實是有一個效能優化的點的。我們這裡的例子非常簡單,它的ViewModel就只是一個int的值,當我們ViewModel很複雜的時候,我們可以使用StoreConnector的distinct屬性進行效能優化。使用方法很簡單:需要我們在ViewModel中重寫[==] and [hashCode] 方法,然後把distinct屬性設為true。
如何處理非同步資料
Redux提供了一種簡單的方法來更新應用程式的狀態以響應同步操作。但是,它缺少處理非同步程式碼的工具。我們如何應對非同步相應呢。
這裡就需要一個interrupt來處理非同步請求,然後再發出新的action通知reducer生成新的State了。 這裡有brianegan大神寫的另外一個幫助在flutter中使用redux處理非同步請求的庫redux_thunk。我會在之後的文章中詳細介紹如何在redux中處理非同步操作。
你認為redux真的適合flutter嗎
我們發現,redux的確能夠在flutter中很好的工作。在react中資料是沒有上行能力的,所以通過資料單向流動形成一個環來進行狀態管理。看上去似乎並沒有把flutter中的優勢完全發揮出來。在這個簡單的例子中我們也可以看出,使用redux還是稍微有些麻煩的,用的不好,可能會陷入redux地獄。學習成本偏高也是它的一大痛點。
當然,redux這套狀態管理架構已經比較成熟,假如您已經習慣redux,也能夠快速通過flutter_redux輕鬆構建屬於您的狀態管理應用。
那麼你現在如何看待redux呢?
寫在最後
本次所用到的程式碼已經上傳Github: github.com/Vadaski/Flu…
這篇文章參考了以下資料
瞭解更多
你能在這些地方瞭解更多關於flutter-redux
- 瞭解flutter-redux:flutterbyexample.com/what-is-red…
- 瞭解redux的問題:medium.com/fluttery/th…
如果您對flutter_redux還有任何看法或者文章的建議或者文章中有任何不對之處,歡迎在下方評論區以及我的郵箱1652219550a@gmail.com留言,我會在24小時內與您聯絡!
按理說下一章我們將探索BLoC在Flutter中的實踐,而BLoC非常Reactive Programming,所以我決定先讓大家瞭解一些dart:Stream的知識再介紹它,所以下一篇文章我們會介紹Stream以及流式程式設計,敬請關注。