Flutter實踐:深入 flutter 的狀態管理方式(3)——Redux與旅途小結
至此,這已經是探索 Flutter 狀態管理方式文章的最後一篇,同時這也是新年後的第一篇文章,之後我將將他們應用在實際專案中開發並將值的學習的專案開源出來。對於其中講解可能有點淺嘗則止,因為給出的例項相對簡單,但相信聰明的程式設計師都會有自己的學習方法,你可以從給出的例項結合自己的所學引深出更好的設計方法。
同時要送給大家的建議是:請在需要這些狀態管理方式時使用它,我見過不少的開發者都有用大刀砍白菜的意思,這並不是一個好習慣,你會發現這些狀態管理方式有時候不但不會使開發簡單,其實還會加大程式碼量,使其變得複雜,對於如何選擇,這裡還不夠說清楚,希望你們都能找到自己的管理應用程式的方式。
所有例項:
github 地址:https://github.com/MeandNi/Flutter_StateManagement
Redux 由 Facebook 2015年提出,是基於 Flux 理念實現的一個響應式框架的狀態管理方式,最早應用於 React 中,而後 React Native 、 Flutter 等多個框架也同樣可以使用。
學習 Redux 之前,請了解 Redux 所能做到的事情:
- 單一狀態來源:整個應用程式的狀態儲存在單個的物件樹中。
- 狀態只讀:更改狀態的唯一方法是發出一個 action(一個描述發生什麼事件的物件)。
- 使用純函式更改狀態:通過 reducers,狀態知道如何通過操作進行改變,reducers便是一個純函式。
[圖片上傳失敗...(image-ef403a-1550631610955)]
如果你用過 React ,相信你對上面的概念已經很清晰了,沒使用過?也 OK,下面慢慢道來....
整體的思路:所有狀態存放在 store 中,Redux 將 store 內所有狀態放入對應的元件中呈現在 Ui 上,使用者與 UI 互動(如點選)發起一個 action(一個描述行為的物件),store 可判別 action型別並作用相應的 reducer(操作改變狀態的純函式),reducer 完成相應改變後將資料放到全域性的 store 中,實現改變。
[圖片上傳失敗...(image-fdc2e1-1550631610955)]
思路簡單,描述起來蠻複雜,其實它的目的就是實現程式碼作用域的分離。
那麼如何讓將以上描述應用於實際的應用程式中呢?
redux 實現計數器應用
其中的實現與 ScopedModel 很相似,抓住兩個要點,如何在 UI 中呈現以及如何 發起 action 改變狀態。
我們首先定義 一個全域性狀態 AppState:
@immutable class AppState { final counter; AppState(this.counter); }
裡面有一個 counter 變數用於計數。
Action
如前面所述,State 的變化,會導致 UI 的變化。但是,使用者接觸不到 State,只能接觸到 UI。所以,State 的變化必須是 UI 導致的。Action 就是 UI 發出的通知,表示 State 應該要發生變化了。
我們這裡有一個促使 counter 增加的 action:
enum Actions { Increment }
reducer
Store 收到 Action 以後,必須給出一個新的 State,這樣 View 才會發生變化。這種 State 的計算過程就叫做 Reducer。
Reducer 是一個函式,它接受 Action 和當前 State 作為引數,返回一個新的 State。
AppState reducer(AppState prev, action) { if (action == Actions.Increment) { return new AppState(prev.counter + 1); } return prev; }
reducer 中判斷了 action 時 Increment 這一型別,將一個新的 AppState 返回到 store。
在 UI 中呈現(StoreConnector)
首先初始化狀態:
final store = new Store(reducer, initialState: new AppState(0));
然後顯示狀態:
new StoreConnector( converter: (store) => store.state.counter, builder: (context, counter) => new Text( '$counter', style: Theme.of(context).textTheme.display1, ), )
通過 converter 屬性拿到 state 中的 counter 屬性。
然後傳入 builder 應用在元件中。
發起 action 改變狀態
new StoreConnector( converter: (store) { return () => store.dispatch(Actions.Increment); }, builder: (context, callback) => new FloatingActionButton( onPressed: callback, tooltip: 'Increment', child: new Icon(Icons.add), ), ),
同樣是使用 StoreConnector,然而這次通過 converter 拿到的是一個store.dispatch(Actions.Increment)
,store.dispatch()
是 View 發出 Action 的唯一方法。而後在元件中呼叫該回調方法即可。
完整程式碼
完整程式碼被放在一個檔案中:樣例程式碼
Redux 應用在 ShoppingCart:樣例程式碼
[圖片上傳失敗...(image-f51297-1550631610955)]
[圖片上傳失敗...(image-c2f10-1550631610955)]
將 Redux 用於更復雜的應用中
在複雜的應用中我們可以將應用程式中的 reducer 根據業務型別分離,例如使用者資訊、產品資訊等不同業務的操作分離到單獨的模組,
[圖片上傳失敗...(image-1c8383-1550631610955)]
而後將其合併:
[圖片上傳失敗...(image-254edb-1550631610955)]
實現上看的分離後,又可只將相應業務的資料放到相應業務根 widget(其子元件的所有資料和操作來自該根元件)
我們將需要的資料及操作到一個物件中,傳遞到元件中:
class DeviceFragment extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: new StoreConnector<AppState, _ViewModel>( converter: _ViewModel.fromStore, builder: (context, vm) { return DeviceList( devices: vm.devices, onStateChanged: vm.onStateChanged, onRemove: vm.onRemove, onUndoRemove: vm.onUndoRemove, ); }), ); } } class _ViewModel { final List<Device> devices; // final bool loading; final Function(Device) onStateChanged; final Function(Device) onRemove; final Function(Device) onUndoRemove; _ViewModel({ @required this.devices, // @required this.loading, @required this.onStateChanged, @required this.onRemove, @required this.onUndoRemove, }); static _ViewModel fromStore(Store<AppState> store) { return _ViewModel( devices: store.state.devices, // loading: store.state.isLoading, onStateChanged: (device) { store.dispatch(editItem(device.copyWith(state: !device.state))); }, onRemove: (device) { store.dispatch(deleteDevice(device)); }, onUndoRemove: (device) { store.dispatch(AddDeviceAction(device)); }, ); } }
這樣使得產生模組化管理的思想!(以上例項來自正在開發的一個真實專案,目前還未開源。)
同時,在實際的應用程式中,你一定需要在程式啟動初期來載入來自雲端或者資料本地的資料,你可以在入口的頁面中接受一個載入資料的函式,放入 initState 函式中。