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 函數中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章