上一篇講到了一個簡單的狀態管理架構—— ScopedModel , 固然,這種簡單的架構會用在商業項目中的機率比較小,本篇則講述另外一個架構: Redux ,一個優雅且實用的狀態管理框架。本篇 Demo 地址:github.com/windinwork/…html
Redux 的概念源於 React,對於不是從事前端工做或者沒有接觸過 React 的人要理解 Redux 會比較繁複。對於不瞭解 Redux 的小夥伴,這裏有兩篇很不錯的文章介紹了 Redux 的概念和相關知識:前端
Redux 入門教程(二):中間件與異步操做github
學習後 Redux 能夠了解到,Redux 主要由涉及下面幾種概念:redux
Redux 的工做流程如圖所示,先用戶發出 Action ,Store自動給 Middleware 進行處理,再傳遞給 Reducer , Reducer 會返回新的 State ,經過 Store 觸發從新渲染 View。這裏的 View 在 Flutter 中以 Widget 的形式存在。bash
以上這部分是 Redux 的內容,比較容易理解。網絡
在 Flutter 中,咱們除了引入 redux 第三方庫以外,還要引入 flutter_redux 第三方庫 ,而且,爲了對異步操做有更好的支持,還要引入 redux_thunk 庫做爲 Middleware ,對 thunk action 有更好的支持。架構
下面來了解 flutter_redux 中的概念。app
StoreProvider ,是一個基礎 Widget ,通常在應用的入口處做爲父佈局使得,用於傳遞 Store 。框架
StoreConnector ,一個能夠獲取 Store 的 Widget ,做用是響應 Store 發出的狀態改變事件來重建 UI。
StoreConnector
中有兩個標記爲@required 的參數,一個是 converter , converter 用於將 store 中的 state 轉化爲 viewModel,另外一個是 builder,builder 的做用是將 viewModel 進一步轉化爲 UI 佈局。
StoreConnector
有一個值得一提的函數:onInit,在這個函數中能夠執行初始化操做。
經過 StoreProvider 和 StoreConnector 就能夠在 Flutter 實現 Redux 的功能了,接下來是具體實踐。
這裏以常見的獲取列表選擇列表爲例子。一個頁面用於展現選中項和跳轉到列表,一個頁面用於顯示列表。
redux: ^3.0.0
flutter_redux: ^0.5.3
redux_thunk: ^0.2.1
複製代碼
Store 全局只有一個,這裏封裝一個建立 Store 的方法。建立Store時,傳入 Reducer ,初始化的 State 和 Middleware 。
Store<AppState> createStore() {
return Store(appReducer, initialState: AppState.initial(), middleware: [
// 引入 thunk action 的中間件
thunkMiddleware
]);
}
複製代碼
State 狀態,須要建立一個 AppState
,做爲整個應用的 state,除此以外,根據不一樣的頁面能夠有不一樣的 State,好比有一個列表頁面 ListPage
,就能夠有一個列表狀態 ListState
,而且 ListState
會放在 AppState
中做爲成員變量進行管理。
// app state
class AppState {
ListState listState;
AppState(this.listState);
}
複製代碼
// list state
class ListState {
bool _init = false; // 列表初始化標誌
List<String> _list = []; // 列表數據
String _selected = '未選中'; // 選中的列表項
ListState(this._init, this._list, this._selected);
}
複製代碼
Action 是 Widget 在用戶操做後發出的通知。對於 ListPage
頁面,建立一個 list_action.dart
文件,用於存在可發出的 Action 。在示例中,須要用到兩個 Action 。一個是加載完成列表數據後發出的 Action ,用於刷新列表,一個是選中列表項時發出的 Action 。
/**
* 加載完成列表數據
*/
class FetchListAction {
List<String> list;
FetchListAction(this.list);
}
/**
* 選擇列表
*/
class SelectItemAction {
String selected;
SelectItemAction(this.selected);
}
複製代碼
Reducer 是一個帶 State 和 Action 兩個參數的純函數,在這裏,只須要關心根據傳入的 Action 和舊的 State,返回一個新的 State。
和 State 同樣,reducer 也分爲應用的 app_reducer
和列表頁面的 list_reducer
。
Reducer是純函數。在示例中經過 app_reducer
返回一個新的 AppState
,經過 list_reducer
分別對 FetchListAction
和 SelectItemAction
進行處理返回一個新的 ListState
。
AppState appReducer(AppState state, dynamic action) {
return AppState(listReducer(state.listState, action));
}
複製代碼
ListState listReducer(ListState pre, dynamic action) {
if (action is FetchListAction) {
return ListState(true, action.list, pre.selected);
}
if (action is SelectItemAction) {
return ListState(pre.isInit, pre.list, action.selected);
}
return pre;
}
複製代碼
在建立 Store 時引入了 thunkMiddleware
,使得項目支持 thunk action。
項目中須要一個加載列表的異步操做,可經過 thunk action 實現具體操做,注意 ThunkAction
是隻有一個 store
參數的函數。
ThunkAction<AppState> fetchList = (Store<AppState> store) async {
// 模擬網絡請求
await Future.delayed(Duration(milliseconds: 3000));
var list = [
"1. Redux State Management",
"2. Redux State Management",
"3. Redux State Management",
"4. Redux State Management",
"5. Redux State Management",
"6. Redux State Management",
"7. Redux State Management",
"8. Redux State Management",
"9. Redux State Management",
"10. Redux State Management"
];
store.dispatch(FetchListAction(list));
};
複製代碼
經過以上代碼,咱們作好了 Redux 的準備工做。接下來即可以佈局頁面。
① 在 main.dart
中,要作的工做是建立 Store,和使用 StoreProider 做爲根佈局。
// 建立Store
var store = createStore();
// 使用StoreProvider做爲根佈局
return StoreProvider<AppState>(
store: store,
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ShowPage(),
));
}
複製代碼
② show_page.dart
,用於顯示選中的列表項和提供跳轉到 ListPage
的按鈕入口。
show_page.dart
中,選中的列表項的數據來自於 Store,因此這裏使用 StoreConnector 來根據數據構建 UI 界面。
StoreConnector<AppState, String>(
converter: (store) => store.state.listState.selected,
builder: (context, selected) {
return Text(selected);
},
),
複製代碼
從上面可見,StoreConnector 須要兩個泛型,一個是咱們建立的 AppState,另外一個是 ViewModel,這裏咱們直接將 String 做爲 ViewModel。
StoreConnector 要定義兩個函數,一個是 converter,從 Store 中拿出選中的列表項數據 store.state.listState.selected,另外一個是 builder,將 converter 返回的 selected 進一步轉化爲界面:Text(selected)。
這就是 StoreConnector 的用法。
③ list_page.dart
list_page.dart
中的內容比較重點,裏面實現了加載列表和點擊列表的功能。
list_page.dart
中一開始要顯示加載中的界面,等待數據成功加載後顯示列表界面。這裏也是用 StoreConnector 構建 UI。StoreConnector 將 AppState 轉換爲 ListState,經過 ListState 判斷當前顯示 loading 界面仍是列表界面。
另外,在 StoreConnector 的 onInit 函數中執行加載列表的操做。
因爲加載列表是一個異步操做,因此要用到以前定義的 fetchList
的 thunk action,這裏經過 store.dispatch(fetchList)
來執行。
StoreConnector<AppState, ListState>(
// 初始化時加載列表
onInit: (store) {
if (!store.state.listState.isInit) {
store.dispatch(fetchList);
}
// 將store的state轉化爲viewModel
}, converter: (store) {
return store.state.listState;
// 經過viewModel更新界面
}, builder: (contxet, state) {
// 根據狀態顯示界面
if (!state.isInit) {
// 顯示loading界面
return buildLoad();
} else {
// 顯示列表界面
var list = state.list;
return buildList(list);
}
})
複製代碼
另外,點擊列表時也要發出一個選中列表項目的 Action ,即以前定義的 SelectItemAction
。
var store = StoreProvider.of<AppState>(context);
store.dispatch(SelectItemAction(list[index]));
// 返回到上一級頁面
Navigator.pop(context);
複製代碼
store.dispatch(action)
發出的 Action 通過 Middleware 和 Reducer 的處理後,轉變成 State,StoreConnector 就會自動地根據 State 重建 UI。
這樣,一個使用 Redux 架構的應用就基本成型了。完整代碼能夠參考github.com/windinwork/…
在 Redux 中,數據老是"單向流動"的,保證了流程的清晰。相對於 ScopedModel 的架構, Redux 無疑更加規範,更易於開發和維護。只是, Redux 會相對地增長複雜性,因此,簡單小型的項目能夠不須要去考慮引進 Redux 這種架構,但對於大型的 Flutter 項目來講, Redux 就十分有用的架構。
因爲 Redux 只有一個應用只有一個 Store ,因此應用須要對整個 AppState 進行維護,在大型客戶端代碼迭代過程當中,會和組件化和單獨模塊編譯運行這種需求相矛盾,這是否會阻礙 Redux 成爲大型項目採用的主要架構的因素呢?
Flutter Redux Thunk, an example finally.