【第1586期】基于Redux/Vuex/MobX等库的通用化状态OOP
前言
今日早读文章由铃盛@Michael Lin投稿分享。
@Michael Lin,RingCentral前端高级工程师,前端架构经验丰富,擅长通用化领域,热爱开源。
GitHub:https://github.com/unadlib
NPM: https://www.npmjs.com/~unadlib
正文从这开始~~
如果你对Redux/Mobx/Vuex等如何更好的OOP设计感兴趣,那么本文将给出一个前端状态库OOP完整的通用化方案。
动机
由于前端单页应用开发日趋复杂,当我们在使用React/Vue时,为了开发复杂的App让我们不得不用到一些状态管理或者状态容器(下文统称为状态库),同时我们也急需一个更容易模块化的模型设计。而前端状态库又百花齐放,无论是Redux/MobX/Vuex以及Angular自带的状态管理,状态库的模块化也一直是最近几年复杂系统中的前端开发领域的新需求。当然Angular来说这个需求早就被Angular框架本身实现了,但对于其他的库这是一个至关重要的问题,因此本文试着探索一套通用于主流状态库的OOP模块化设计。
通用化状态模块
通常情况下,前端中大型项目的架构设计中常见于采用面向对象编程(OOP),而采取哪种前端状态库等问题又经常变成可争论热点:
到底是Redux还是MobX更适合用于React?
Redux适合应用于OOP吗?
MobX的observable在React带来利弊如何权衡?
在Vue中Vuex如何OOP?
此外,大部分情况下的通用化JavaScript更多针对于JavaScript的运行环境,一旦某种架构设计选定了某个状态库,那么将意味着该架构难以脱离此状态库的使用,任何系统基于此架构都将基于这样的状态库。但更好的前端架构会包含更灵活的可选性与可扩展性,尤其有类似典型的集成业务之类通用化需求的前端部分,其中可体现于View Render库有可选余地,甚至状态库有可选余地,例如在主流方案中React+Redux/React+MobX/Vue+Vuex/Angular等有选择余地,那么它带来的问题便是通用化状态库。
我们需要因此解决这几个问题:
基于Redux/MobX/Vuex 等状态库的OOP的设计,这也是最重要的,尤其对Vue和React而言。
被封装的OOP设计是否足够简单易用,同时它们具有相当灵活性。
从DDD角度说,在复杂的domain modules间的依赖关系需要IoC,它们之间的启动逻辑有依赖关系,那么必然有类似事件机制或者module生命周期的引入。
为解决以上几个问题,通用化OOP封装和模块标准化生命周期或者事件机制变得不可或缺。
提出解决方案
基于这样通用化的概念,我们提出新的通用化状态模块的lib —— usm。
首先,它应该能解决是基于Redux/MobX/Vuex等状态库的OOP设计。
这是典型的Redux Counte例子:
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.subscribe(() => console.log(store.getState()))
store.dispatch({ type: 'INCREMENT' })
store.dispatch({ type: 'DECREMENT' })
而这是基于usm的Counter例子:
import Module, { state, action } from 'usm';class Counter extends Module {
@state count = 0;
@action
increase(state) {
state.count += 1;
}
@action
decrease(state) {
state.count -= 1;
}}
首先我必须承认Redux在immutable类型的状态库中绝对是最好的库之一,在这里我无意要讨论一些Redux的缺点,我们想探讨的是如何利用Redux进行更好的OOP设计。我们希望基于Redux的模型可以更加直观和简洁,就像上面提到的基于ES6+的class的Counter的OO例子一样,如果这样的OO范式它同时还是通用化的状态模型,一个更好的统一状态库封装, 这无疑可以给开发者带来会有一种更灵活和更友好的编程体验(当然也包括易于阅读/维护等)。usm正好解决了这些问题,并且目前usm支持Redux/MobX/Vuex/Angular。
USM特性
通用化状态模块
标准化模块生命周期
可选事件系统
支持无状态最小化模型
支持Redux/MobX/Vuex/Angular
USM装饰器
usm提供@state用于包装一个带状态的变量,@action用于包装一个改变状态的函数(函数传入的最后一个参数均为当前state对象),除此以外和一个普通的class封装的OO模块没有区别。
USM模块生命周期
如果有必要,usm提供五个支持异步的生命周期函数:
moduleWillInitialize
moduleWillInitializeSuccess
moduleDidInitialize
moduleWillReset
moduleDidReset
它们的运行顺序如下图所示:
需要特别说明的,usm之所以提供生命周期是因为在大部分复杂的领域模块场景下,模块间的启动依赖常常是必须的,但是在不必使用它们的时候,它们的设置当然都是可以省缺的。
理想中的架构设计
在复杂前端模块系统中, 这也许是一个比较典型的模块化架构设计,它包含以下几个部分:
生命周期
Store订阅器
事件系统
State
依赖模块
领域模型
当然在这里只是提出这样的设想,或许某些架构运用场景下可能是这样设计模型的扩充或删减。
Todo简单例子
// if necessary, you can use `usm-redux`/`usm-mobx`/`usm-vuex` with states.import Module, { state, action } from 'usm';
class Todos extends Module {
@state list = [];
@action
addTodo(text, state){
state.list.push({text});
}
@action
toggle(id, state){
const todo = state.list.find(item => item.id === id);
todo.completed = !todo.completed;
}}
结论
当你使用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。
最后,我们可以提出一个值得思考的问题:
从OOP角度来说,前端状态库的选择真的很重要吗?
最后,为你推荐
【第1581期】 2019 React Redux 完全指南