为什么我不用 Redux

第一次接触 Redux,是 16 年 8 月的时候了,当时主要还是在写 AngularJS,尝试了 React 和 Redux 之后,React 本身我是觉得不错的,但是 Redux 过于麻烦,文件数量太多,实在没感觉。后来了解了 Mobx 之后,React 的项目都是使用 Mobx 来做状态管理的。

最近在公司项目,因为内部基建的原因,所以一开始都是用了 dva(redux/redux-saga) 来做的,但越写越闹心,做到一半受不了改用 Mobx 了。个人认为 Redux 的问题如下:

  1. 对 TypeScript 支持不好。

    由于 Redux Store 和 Component 之间相隔了 selector 和 action,这意味着要多写很多类型。selector 还好,要么能得到 rootState 的类型,要么直接类型断言。但 action 就比较尴尬,一个 actionCreator 只是单纯的返回一个 FSA。而 type 还是用常量或者字符串来表示,这就意味着不便于语言层面上的引用追踪,意味着依赖于运行时,存在不确定性。这也导致所有的 reducer 本质上是零引用的,自然你给他定义参数类型,也只能方便该 reducer 本身使用,而不能对使用者进行约束,相反我们只能在 actionCreator 上面进行约束。

    我认为,Redux 并不适合用 TypeScript 来写,因为 Redux 的写法比较函数式,而不像 Mobx 是更加面向对象。且不说上面的 action 和 selector 的问题,我们想得到 mapStateToProps 和 mapDispatchToProps 的返回值类型都必须通过污染运行时才能得到,如下:

    interface IOwnState { }	// 因为 state 本身是一个 object,所以只能单独定义它的接口
    
    interface IOwnProps { }	// Props 的接口,我们期望使用该组件的时候 props 按这个来校验
    
    const mapStateToProps = (state, ownProps: IOwnProps) => ({
      ...ownProps,
    });
    
    const mapDispatchToProps = dispatch => ({
    
    });
    
    const mapState = getReturnOfExpression(mapStateToProps);	// 为了得到 mapStateToProps 的返回值类型,下同
    const mapDispatch = getReturnOfExpression(mapDispatchToProps);
    type IMapStateToProps = typeof mapState;
    type IMapDispatchToProps = typeof mapDispatch;
    type IProps = IMapStateToProps & IMapDispatchToProps;	// 这个才是组件实际具备的 props 类型
    
    class InOutBandwidthChart extends React.Component<IProps, IOwnState> {
    
      render() {
        return null;
      }
    }
    
    export default connect<IMapStateToProps, IMapDispatchToProps, IOwnProps>(
      mapStateToProps,
      mapDispatchToProps
    )(InOutBandwidthChart);	// 我们并不期望使用该组件按照 IProps 来校验,而是期望用 IOwnProps 来处理,这里只能这么做。
    
    

    这个写法是不是很复杂呢。如果使用 Mobx,那么一切都会自然简单很多,我们不需要把组件数据都塞到 state 上面,完全可以直接定义在类的属性上面,我们可以直接定义的时候声明类型,就不需要单独写一个 state interface 出来了。另外,也不需要 connect 操作,直接使用 xxxStore 上的数据和方法,非常面向对象。

  2. 过于提倡纯函数,Component 过于纯粹。

    Redux 提倡纯函数的思想,然而现实中导出都是副作用。Redux 不处理副作用,转而把坑丢给了 redux-thunk 或 redux-saga 诸如此类的库去处理。redux-thunk 和 redux-promise 多多少少污染了原先的 dispatch 方法,导致 dispatch 变得不纯。而 redux-saga 则标榜不污染 dispatch,并把副作用放到了 effect 去处理。

    这种做法其实有好有坏,有些人觉得好,有些人觉得不好。我个人是不太喜欢这种思想的。原因是它导致我在 Component 中没法进行一些异步的逻辑处理,准确的说,是我没办法在一个 effect 之后去处理另一个 effect。除非把另一个 effect 的 dispatch 作为回调传给上一个 effect,或者把两个回调合并为一个。

    先说把第二个 effect 的 dispatch 触发作为回调传给第一个 effect,这导致 actionCreator 返回的不再是一个 FSA(Flux Standard Action)。FSA 不允许出现除了 type,payload,error,meta 之外的属性。把回调放到 payload 中也不是很合适。当然也可以完全没必要遵循这套约束,但是我个人认为 Redux 本身就是一堆约束组成的,再者我也不认为这是一个比较好的方法,污染了 payload 或者 action,也不好处理多层嵌套回调的情况。并且通过回调的方式,我们又要把第二个回调的参数放哪里呢?怎么想都不太恰当。

    如果把他们合并为一个 effect 处理。举个例子我们希望删除一个实例之后重新获取全部实例,正常的思路是他们是两个方法,我们先执行删除操作后再执行获取全部实例操作。但现在反而要专门写一个 effect 去顺序执行这两个,显得啰嗦。同时,我们还需要得到重新获取全部实例操作的参数,这些参数如果从 Component 传递过来,同样导致 action 写法不统一,混乱,违背了 Redux 和 redux-saga 不污染 dispatch FSA 的思想。也有人会说那我把那些数据放到 redux store 上不就好了,这样的话一方面增加了项目复杂性,多了很多 actionCreator,reducer,selector 甚至 constant。其次,这会带来另一个问题,什么数据放到 redux store 上面什么数据放到 react component 中?本来我是想,只有需要跨组件共用或者需要缓存的数据才考虑放到 redux store 上,但现在因为重新获取操作需要页数这些信息而我又不想通过 action 间接传递,就只能把他放到 redux store 上。想想都混乱。

    说这么多,其实就是,我认为 Redux 和 redux-saga 有很多不成文的规定和约束以及它自己的思想,开发者赢尽可能避免去违背,否则,为什么你还要用它们呢?

基于以上两点,我放弃了 redux,转而使用了 Mobx。

花了一个下午和晚上进行重构,删除代码 1900 多行,新增 800 多行。可见 mobx 在实现相同的功能下比 Redux 简洁很多。

一直都挺喜欢 Mobx 的,感觉它更加智能高效,自动管理状态节省了很多工作。如果真的要说个不是的话,可能就是当一个数据的依赖集过大时,数据变更可能变得比较难以追溯。通过开启 Mobx 严格模式,可以限制可观察数据变更在可观察方法里面进行。但当页面数据变得庞大复杂起来时,还是有可能会遇到这个问题的。好在 mobx-dev-tools 工具也比较强大,只要注重工程质量和代码规范的话,这个应该还是很难成为一个问题的。

而 Redux 的话,其实一开始用的时候我也在考虑,有两种方案。一种是数据是都要放到 Redux 里面,这样一来大量业务组件都成为了无状态组件,但写起来就会很麻烦了,会有大量的 selector 和 action,我不明白为什么有人会喜欢这样子做的。另一种是,组件自身的一些状态数据不涉及业务的,放到组件内,比如像一个按钮是否 disabled 这些,这个是目前大多数人比较认可的,但是某个状态是否不需要被共享,不需要被 Redux 使用有可能随着业务变化而改变,就像页数这个看似应该存放在组件内部的,有可能因为我们需要在一个删除并更新表格的操作中,变为一个存放于 Redux 中的数据,或者因为我们希望缓存,也可能需要放到 Redux 中去做持久化处理。

Redux 那一套其实我用的不是非常多,但是最近的使用确实遇到了很多问题不知道怎么处理比较好,也不是解决不了,但只是觉得这么做不太合适,违反了 Redux 和 redux-saga 所标榜约束的一些东西。还是那句话,既然我们无视那些成文或者不成文的规定或约束,那么为什么我们还要用它呢?

上面比较多都是吐槽 Redux 的种种,关于 Mobx 本身我是没怎么介绍的,感兴趣的自己去看下文档。Mobx 其实比较类似 Vuex。对比 Redux 和 Mobx,个人是比较青睐 Mobx 这一套的,写起来非常爽,搭配 TS 的话,其实也可以一定程度避免很多人吐槽的 Mobx 的数据混乱的问题,其实这个本质上还是开发者编程能力的问题,只要自己注意遵循一些规范和约定,Mobx 在大型项目中也是妥妥的。