关于 Redux 的思考

227 阅读4分钟

刚开始使用 redux 是只是忙于学习他怎么用,从未考虑过他是不是有什么缺点,直到看到了 dva/rematch 时,才意识到 redux 在某些层面上确实存在一些“问题”,下面是一些思考的记录

Redux 的学习使用

开始学习 redux 时,会遵照在网站上查询到的如下内容书写:

import { createStore } from 'redux';

const reducer = (state,action) => {
    switch(action.type){
        // todo...
    }
}

const action = data => ({
    type: 'xxx',
    data
})

const store = createStore(reducer)

store.dispatch( action(2) )

上面的代码展示了创建 store,reducer,action 到调用 action 的过程,如果你熟悉 redux,会对这些代码非常熟悉,当你需要新添加一个 action 时,你都需要添加对应的的 reducer 去处理,不然就做不到改变状态,这些的写法很规范的展示了单项数据流,数据流向清晰。
但是会在项目中出现很多个 action 已经 action.type。而且可能会将多个 action 的处理放在一个 reducer 中,这也会导致一个 reducer 过于臃肿,使后续的开发和维护变得艰难。

这时,可能会做以下的优化:

  1. 将 action.type 单独写出来,方便调用
  2. 适时的将 reducer “减肥”,防止其中过多的处理代码

要实现上述两点,需要每个参与开发人员进行规范的定义,还要防止 action.type 出现重复。这样子可能会使参与开发者的负担加重,因为还要思考注意一些非项目逻辑的问题。

说道这里,其实 redux 的“问题” 已经暴露出来了,它需要开发者去维护很多东西,因为它足够“简单”。所以不帮你处理过多的事情。及时你想执行一个 function action ,你也需要自己去找到 reduxthunk/redux-sagas 中间件进行处理,这在无形中为你增加了一些“额外的工作量”。

现在设想一下,如果存在一种“方便”的 redux,可以通过以下便捷的方式实现项目使其需求

import {init} from ‘xxx’;

const moduleA = {
    state: {},
    reducers: {
        in: () => {}
    }
    ....
}

const store = new init({moduleA})

store.dispatch({
    type: 'in',
    data: 2
})

这种方式的 “redux” 简化了定义过程,他将 reducer 和 action.type 进行了合并,但是同时语义更加清晰。键表示 action.type 而值则表明了这个 action 想做的操作,这样子就可以是 action.type 和要执行的 reducer 对应起来,就不用再向以前一样,那这个 action.type 去满世界的寻找 reducer 进行维护。

同样的,这种方式没有解决 action.type 重名的问题,但是可以思考一下方式:

import {init} from ‘xxx’;

const moduleA = {
    name: 'moduleA',
    state: {},
    reducers: {
        in: () => {}
    }
    ....
}

const store = new init({moduleA})

store.dispatch({
    type: 'moduleA/in',
    data: 2
})

通过给每个 module 添加一个 name 属性,调用 action 时加上前缀,可以极大限度的解决重名的问题。同时通过 module 的分割,可以把 redux 整个状态树切割成小块,使整个树的结构更加清晰。

如果实现了上述的 “redux”,还可以减少书写 redux 模板代码的次数,而且 reducer 变成了一个对象,也就解决了 switch 语句变得臃肿难维护的问题。

仍然存在的问题

当需要进行异步请求的时候,redux 天生不支持 function action,我们通过一些中间件 reduxthunk/redux-sagas,可以增强 action 的功能,达到执行异步的目的。但是此时,异步/同步 action 是混合在一起的定义的(可能有些项目中时分开的),这会导致在阅读时产生极大的障碍,因为不得不在一堆同步/异步中找到我们的目标。一般期望情况下,需要将异步与同步区分开来,方便维护和阅读。可能是下面这样子:

import {init} from ‘xxx’;

const moduleA = {
    name: 'moduleA',
    state: {},
    reducers: {
        in: () => {}
    },
    effects: {
        be(){
            // 异步操作
        }
    }
    ....
}

const store = new init({moduleA})

store.dispatch({
    type: 'moduleA/in',
    data: 2
})

这样子,同步和异步 action 就分开定义,更好地阅读和维护。因为 action 在调用时我们不知道他是异步还是同步的。

上面的代码片段即使你是一个 redux 的初学者,你也能在不知道reduxthunk/redux-sagas等中间件的时候很好的使用 redux 进行状态管理,而且理解上的成本也很低。最主要的是简化了整个流程的创建步骤和过程。

正片开始

当你也有这些思考的时候,你就需要了解下 dva/rematch 这些在 redux 基础上进行封装的状态管理库,他们实现了上述的所有功能,使你只学习简单的 api 就可以愉快的使用 redux,而不需要关注太多与你实际代码逻辑无关的问题。

这里简单的提一下,dva/rematchvuex 的思想类似,他们都是提供了极简的 api,就可以实现应用状态管理。