V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
unadlib
V2EX  ›  JavaScript

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

  •  
  •   unadlib · 2019-04-19 13:26:35 +08:00 · 3315 次点击
    这是一个创建于 2078 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    USM Repo: https://github.com/unadlib/usm

    1 条回复    2019-04-19 14:51:21 +08:00
    gouflv
        1
    gouflv  
       2019-04-19 14:51:21 +08:00
    前端需要一个领域模型,不依赖视图层的具体框架,这点 usm 实现的很棒,类型系统支持的也很好。

    另外,我的理解,flux 和 OOP 走的本来就是两条路。OPP 强调抽象、复用,可以说简单直接; flux 更强调约束,但统一范式能带来数据可控。两者还是从场景来取舍。大部分项目应该是倾向 OOP 的,因为模块本身都不重,也没有特别严重的多人协作的问题。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1077 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 19:33 · PVG 03:33 · LAX 11:33 · JFK 14:33
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.