前端状态管理思考与实践

September 11, 2017

目前已开源 rab 仓库 文档传送门

背景

再过去的两年我一直在维护大量的 OA 应用,所以在 redux 的基础上封装了路由,订阅器,action 生命周期等功能,方便各个 OA 系统开箱即用使用。 一个极简的 demo 如下: 注: 其中的 reducers 有 start,next,throw,finish 四个生命周期,方便进一步简化 redux 异步处理的情况下的样板代码

import React from 'react';
import rab, {connect, createModel, put, call} from 'rab';
import {Router, Route} from 'rab/router';

function stop(time) {
    return new Promise((res, rej) => {
        setTimeout(function () {
            res();
        }, 2000);
    });
}

const app = rab();

// 2. Model
let count = createModel({
    namespace: 'count',
    state: {
        num: 0,
        loading: false
    },
    reducers: {
        add(state, action) {
            console.log(action.payload);
            return Object.assign({}, state, {num: state.num + 1})
        },
        asyncAdd(state, action) {
            return Object.assign({}, state, {num: state.num + action.payload})
        },
        asyncMinus: {
            start(state, action){
                return Object.assign({}, state, {loading: true});
            },
            next(state, action){
                return Object.assign({}, state, {num: state.num + action.payload})
            },
            throw(state, action){
                return Object.assign({}, state, {num: state.num + action.payload})
            },
            finish(state, action){
                return Object.assign({}, state, {loading: false});
            }
        }
    },
    actions: {
        asyncAdd: (a, b, c) => async ({getState, dispatch}) => {
            await stop();
            return 100;
        },
        async asyncMinus() {
            await stop();
            return -100;
        }
    },
    subscriptions: {
        init({history, dispatch}){
            history.listen((location) => {
                console.log('init------------>', location)
            })
        }
    }
})

app.addModel(count);

// 3. View
const App = connect(({count}) => ({
    count
}))((props) => {
    return (
        <div>
            <h2>{ props.count.num }</h2>
            <h2>{ !props.count.loading ? 'finish' : 'loading' }</h2>
            <button key="add" onClick={() => {
                put({type: 'count.add', payload: {a: 1}});
            }}>+
            </button>
            <button key="asyncadd" onClick={() => {
                props.dispatch(count.actions.asyncAdd());
            }}>ASYNC ADD
            </button>
            <button key="asyncminus" onClick={() => {
                put({type: 'count.asyncMinus', payload: {a: 1, n: 2}});
            }}>ASYNC Minus
            </button>
        </div>
    );
});

// 4. Router
app.router(({history}) => {
    return (
        <Router history={history}>
            <Route path="/" component={App}/>
        </Router>
    );
});

// 5. Start
app.start('#demo_container');

问题

在今年开始,我主要负责实现一系列的大象富应用,如实时协作的 excel,word,ppt,脑图等等。考虑到后期的维护性,我们全面拥抱了 TS ,这时候发现之前的状态管理存在一些问题:

  1. 存在 字符串 进行 dispatch 的情况,如上面例子中第 57 行的 count.asyncMinus
  2. 整体集成方式过于耦合,在富应用的开发环境下,我们更希望能基于依赖注入的方式将各个模块解耦
  3. 局部状态过于复杂,在实际应用开发过程中,会发现存在很多局部的 UI 状态,这些是和业务建模相关的数据无关的,但是由于我们组件化拆分的比较彻底,就会导致如果不放在全局统一的数据源下,就需要层层的 props 进行传递,代码的维护性和可读性都会有影响
  4. 需要自行的处理

目录