基於Redux/Vuex/MobX等庫的通用化狀態OOP

unadlib發表於2019-03-25

architecture

如果你對Redux/Mobx/Vuex等狀態庫如何更好的OOP設計感興趣,那麼本文將給出一個前端狀態庫OOP完整的通用化方案。

動機

由於前端單頁應用開發日趨複雜,當我們在使用React/Vue時,為了開發複雜的App讓我們不得不用到一些狀態管理或者狀態容器(下文統稱為狀態庫),同時我們也需要一個更容易模組化的模型。

前端狀態庫百花齊放,無論是Redux/MobX/Vuex以及Angular自帶的狀態管理,狀態庫的模組化也一直是最近幾年複雜系統中的前端開發領域的新需求。Redux是具有不可變資料結構的可預測狀態容器。MobX是一種可觀察的狀態管理庫。Vuex是在Vue中具有可觀察的集中狀態管理庫。而對於模組化而言, Angular已經有了自己的實現, 但對其他狀態管理庫卻是越來越需要在複雜的前端專案中處理這一新的要求。

在本文中, 讓我們探索一種新的OOP模組化設計, 該模組化設計對主流的狀態管理庫都具有普遍性支援。

通用化狀態模組

通常情況下,前端中大型專案的架構設計中常見於採用物件導向程式設計(OOP),在決定狀態管理庫時, 經常會提出以下問題:

  • 到底是Redux還是MobX更適用於React?
  • Redux適合應用於OOP嗎?
  • MobX的observable在React帶來利弊如何權衡?
  • 在Vue中Vuex如何OOP?

此外,大部分情況下,前端架構與狀態管理緊密耦合。一旦選擇了狀態管理庫, 就很難在沒有重大重構的情況下切換到另一個庫。因此, 任何使用該架構的系統也必須使用相同的狀態庫。但更好的前端架構設計應該是靈活和可擴充套件。特別是對於旨在實現整合目的的設計, 以適應目標環境和SDK架構則非常重要。為了建立與z主流框架 (React+Redux/React+MobX/Vue+Vuex/Angular) 配合使用的模組, 我們需要通用化狀態模組設計。

設計目標

  • 基於Redux/MobX/Vuex 等狀態庫的OOP的設計,這也是最重要的,尤其對Vue和React而言。
  • 被封裝的OOP設計是否足夠簡單易用,同時它們具有相當靈活性。
  • 從DDD角度說,在複雜的domain modules間的依賴關係需要IoC,它們之間的啟動邏輯有依賴關係,那麼必然有類似事件機制或者module生命週期的引入。

為解決以上幾個問題,通用化OOP封裝和模組標準化生命週期或者事件機制變得不可或缺。

提出解決方案

基於這樣通用化的概念,我們提出新的通用化狀態模組的庫 —— usm

首先,它應該能解決是基於Redux/MobX/Vuex等狀態庫的OOP設計。

讓我們從典型的Redux計數器示例開始:

import { createStore } from 'redux';

function counter(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
}

const store = createStore(counter)

store.dispatch({ type: 'INCREMENT' })
store.dispatch({ type: 'DECREMENT' })
複製程式碼

USM支援Redux、MobX、Vuex和Angular。它提供了usmusm-reduxusm-mobxusm-vuex四個子包。下面是使用usm-redux的計數器例子:

import Module, { state, action } from 'usm-redux';

class Counter extends Module {
  @state count = 0;

  @action
  increase(state) {
    state.count += 1;
  }

  @action
  decrease(state) {
    state.count -= 1;
  }
}

const counter = Counter.create();

counter.increase();
counter.decrease();
複製程式碼

上面相同計數器的實現基於物件導向的正規化。ES6類語法的使用直觀而簡潔。如果這種設計可以通用於任何使用的狀態管理庫, 無疑將為開發人員帶來更靈活、更友好的開發體驗, 以及更好的可讀性和可維護性。

在本示例中使用了usm-redux, 它基於Immer實現了從mutable操作得到immutable資料。

我必須承認Redux在immutable型別的狀態庫中絕對是最好的庫之一,在這裡我無意要討論一些Redux的缺點,我們想探討的是如何利用Redux進行更好的OOP設計。我們希望基於Redux的模型可以更加直觀和簡潔,就像上面提到的基於ES6+的class的Counter的OO例子一樣,如果這樣的OO正規化它同時還是通用化的狀態模型,一個更好的統一狀態庫封裝, 這無疑可以給開發者帶來會有一種更靈活和更友好的程式設計體驗(當然也包括易於閱讀/維護等)。usm正好解決了這些問題。

下面演示在React中如何使用react-reduxusm-redux連線:

// index.js
export const counter = Counter.create();

ReactDOM.render(
  <Provider store={counter.store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
複製程式碼
// app.js
import { connect } from 'react-redux';
import { counter } from './';

export default connect(
  state => ({ count: state.count })
)( props => 
  <div>
    <button onClick={() => counter.increase()}>+</button>
    {props.count}
    <button onClick={() => counter.decrease()}>-</button>
  </div>
);
複製程式碼

下面是使用mobx-reactusm-mobx的連線例子:

// index.js

export const counter = Counter.create();

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
複製程式碼
// app.js
import { observer } from 'mobx-react';
import { counter } from './';

export default observer(() =>
  <div>
    <button onClick={() => counter.increase()}>+</button>
    {counter.count}
    <button onClick={() => counter.decrease()}>-</button>
  </div>
);
複製程式碼

使用usm-redux+react-reduxusm-mobx+react-redux與React的結合例子足以證明, 即使使用的聯結器不同, 但狀態模組的核心業務邏輯是相同的。這是我們提出的通用化狀態模組的核心原則。

USM 目前支援Redux, MobX, Vuex和Angular。

特性

  • 通用化狀態模組
  • 標準化模組生命週期
  • 可選事件系統
  • 支援無狀態最小化模型
  • 支援Redux/MobX/Vuex/Angular

裝飾器

usm提供@state用於包裝一個帶狀態的變數,@action用於包裝一個改變狀態的函式(函式傳入的最後一個引數均為當前state物件),除此以外和一個普通的class封裝的OO模組沒有區別, usm同時也提供了通用的@computed

class Shop extends Module {
  @state goods = [];
  @state status = 'close';

  @action
  operate(item, status, state) {
    state.goods.push(item);
    state.status = status;
  }
  //this.operate({ name: 'fruits', amount: 10 }, 'open');
}
複製程式碼

模組生命週期

usm提供五個支援非同步的生命週期函式:

  • moduleWillInitialize
  • moduleWillInitializeSuccess
  • moduleDidInitialize
  • moduleWillReset
  • moduleDidReset

它們的執行順序如下圖所示:

lifecycle

需要特別說明的,usm之所以提供生命週期是因為在大部分複雜的領域模組間場景下,這些模組生命週期可用於協調模組初始化時的依賴關係。 當然,在不必使用它們的時候,它們的設定都是可以省缺的。

理想中的架構設計

flow chart

在複雜前端模組系統中, 這也許是一個比較典型的模組化架構設計,它包含以下幾個部分:

  • 生命週期
  • Store訂閱器
  • 事件系統
  • State
  • 依賴模組
  • 領域模型

在這裡只是提出這樣的設想,或許某些架構運用場景下可能是這樣設計模型的擴充或刪減。

結論

USM是一種模組設計, 它希望將在不同檢視層 (如React、Vue和Angular) 的組合中使用Redux、MobX和Vuex的差異聯絡在一起。它旨在幫助您構建可用於任何前端架構的庫。

而當你使用React+Redux/React+MobX/Vue+Vuex等庫或者框架組合進行開發時,希望usm是在你的應用系統模組化不錯的選擇,尤其它可能是你在使用React/Vue等UI構建庫時缺少的那塊重要的模組化拼圖。

換句話說,如果你使用usm進行OOP架構設計,那麼你的系統不僅可以減少不同狀態庫的boilerplate,尤其像Redux這樣boilerplate較多的庫而樣應該有很大的幫助。最重要的是,usm可以讓你需要的OOP架構的模組化變得簡潔而直觀,甚至usm可以讓你的業務程式碼相容各種狀態庫,無論是Redux/MobX/Vuex還是Angular,而且如果你用的UI元件庫正好也相容React/Vue/Angular,那麼你的應用將快速無縫使用React/Vue/Angular。

USM允許您跨框架共享業務邏輯庫, 而無需考慮它們所使用的框架。

最後,我們或許提出一個值得思考的問題:

從OOP角度來說,前端狀態庫的選擇真的那麼那麼的重要嗎?

相關文章