基于Redux/Vuex/MobX等库的通用化状态OOP

1,767 阅读7分钟

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角度来说,前端状态库的选择真的那么那么的重要吗?