本文首发于知乎 pure render 专栏
毫无疑问,Redux 与 MobX 是 React 生态中最火热的状态管理工具,社区也一直没有停止对上述两者的讨论。近期,团队小伙伴 黄子毅 的文章 Mobx 思想的实现原理,及与 Redux 对比,以及正在与我一起翻译 MobX 中文文档 岳逢楽 同学的 如果用Redux不爽的话,那就试试MobX吧,都对此发表了自己的观点。在不久前结束的 React Conf 2017 中,Preethi Kasireddy 也做了相关分享,MobX vs Redux: Comparing the Opposing Paradigms,让我们来看看她的观点是怎样的。
状态管理上的异同
Redux / MobX 均为客户端开源状态管理库,用状态来描述 UI 界面,它们与 React 都不具有强绑定关系,你也可以配合别的框架来使用它们。当然,与 React 是再合适不过的了,React 作为 View 层的框架,通过 Virtual DOM 机制来优化 UI 渲染,Redux / MobX 则提供了将相应状态同步到 React 的机制。
Redux 是 Flux 体系很好的衍生。Flux 具有单向数据流的特点,View 层通过触发注册过 dispatcher 的 action,后由 dispatcher 更新相应的 store。同样 Redux 也具有单向数据流的特点和在 View 层触发 action,但与 Flux 不同的是,Flux 可以具有多个 store,而 Redux store 是单一数据源。Redux 没有 dispatcher 的概念,转而使用纯函数(pure function)代替。Redux store 是不可变的(Immutable)。
MobX 类似于 Excel 电子表格,你的数据存放在单元格中,是 observable 的。在 MobX 中具有两种 derivation,一种是 computed value,它是纯函数,相当于 Excel 中的公式,依赖于 observable 数据产生。另一种是 reactions,不同于 computed value 产生新的值,它主要处理副作用(side effect),相当于得到 Excel 公式的结果后,把它们显示在屏幕上。
Redux 与 MobX 的不同主要集中于以下几点:
- Redux 是单一数据源,而 MobX 往往是多个 store。MobX 可以根据应用的 UI、数据或业务逻辑来组织 store,具体如何进行需要你自己进行权衡。
- Redux store 使用普通的 JavaScript 对象结构,MobX 将常规 JavaScript 对象包裹,赋予 observable 的能力,通过隐式订阅,自动跟踪 observable 的变化。MobX 是观察引用的,在跟踪函数中(例如:computed value、reactions等等),任何被引用的 observable 的属性都会被记录,一旦引用改变,MobX 将作出反应。注意,不在跟踪函数中的属性将不会被跟踪,在异步中访问的属性也不会被跟踪。
- Redux 的 state 是只读的,只能通过将之前的 state 与触发的 action 结合,产生新的 state,因此是纯净的(pure)。而 MobX 的 state 即可读又可写,action 是非必须的,可以直接赋值改变,因此是不纯净的(Impure)。
- Redux 需要你去规范化你的 state,Immutable 数据使 Reducer 在更新时需要将状态树的祖先数据进行复制和更新,新的对象会导致与之 connect 的所有 UI 组件都重复渲染。因此Redux state 不建议进行深层嵌套,或者需要我们在组件中用 shouldComponentUpdate 优化。而 MobX 只自动更新你所关心的,不必担心嵌套带来的重渲染问题。
- 在 Redux 中区分有 smart 组件与 dumb 组件,dumb 负责展示,smart 负责状态更新,数据获取。而在 MobX 中无需区分,都是 smart,当组件自身依赖的 observable 发生变化时,会作出响应。
两者之争
从学习成本上而言,MobX 是常见的 OO 范式,与 Redux 基于函数式的理论相对比不具有新的概念,对于深入传统面向对象的程序员而言,更为友好。MobX 是 magic 的,通过 computed value、observer class、autorun function 对于 View 层的输入逻辑进行了抽象,我们只需要在 View 层对相应的 observale 数据进行访问即可完成自动变更。Redux 尽管源码简洁,30 分钟即可读完,但一旦开始构建复杂的应用,你就需要开始考虑如何规范化你的 state,如何使用可记忆的 selector 函数(connent 中 mapStateToProps 中对数据的衍生),如何更好地处理异步等等。
从代码量而言,MobX 由于内置抽象,需要写的代码比 Redux 少了很多。对于一个简单变更状态的操纵而言,Redux 需要写 action,在 View 层调用 action,根据相应 action type,写对应 reducer,MobX 只需要简单一行即可搞定。
从可调试性、可预测性和可测试性上而言, Redux 均优于 MobX。Redux 是简洁、纯净的,能够进行时间回溯,从 state -> view -> action -> reducer 形成单向闭环,使可调试性和可预测性较好,而对于 action 和 reducer 均可单独测试。而 MobX 内部较复杂,同时为了代码的简洁,可在 View 层直接对 observable 对象进行操作,会使发现问题变得困难。正因如此,MobX 提供了 useStrict 进行限制,只能通过 action 来修改 state。另外 Redux devtool 的完善性和体验也要好于 MobX devtool。
从模块化的角度来看,MobX 的 store 是分散的,可以用于封装相应的业务逻辑,Redux 是单一 store,全局共享,没有所谓的模块化,最多只是一些拆分。对于你的组件而言,你需要去共享 state 时,单一数据源便是好的。当你的应用复杂后,对于 MobX 分散 store 间的协调、分享数据、相互引用就会很复杂。
在可扩展性和可维护性方面,Redux 的所有修改会被集中化处理,严格按照一个接着一个的顺序执行,不需要担心竞态的出现。而 MobX 无法保证严格的顺序执行,改变可以发生在各个地方,随着时间的推移,应用可能会出现维护问题。
而在社区方面,Redux 取得完胜。
如何选择
最初我们是使用 React API setState 来维持组件状态,但伴随应用越来越复杂,内部 state 的管理也变随着复杂,组件间需要共享 state,只能提升到父级。遥远的叶子节点需要 state,必须从父级逐层传递,中间的组件将出现 props 冗余。这时就需要 Redux / MobX 将组件内部的状态都抽离出来,做统一管理。
Preethi Kasireddy 认为需要快速开发简单应用的小团队可以考虑使用 MobX,因为 MobX 需要开发的代码量小,学习成本低,上手快,适合于实时系统,仪表盘,文本编辑器,演示软件,但不适用于基于事件的系统。
而 Redux 适用于大团队开发复杂应用,Redux 在可扩展性和可维护性方面可以 hold 住多人协作与业务需求多变,适合商业系统、基于事件的系统以及涉及复杂反应的游戏场景。
Redux 对 action / event 作出反应,而 MobX 对 state 变化作出反应。在更新逻辑方面,MobX 是在 store 内进行相应封装,假设一个 UI 操作需要更新多个 store 内的值时,那么我们需要多次触发对应 action,或者在多个 store 上再添加一个 store 去进行控制,这都不如 Redux 来得优雅,Redux 只需要触发一个 action,多个 reducer 可响应相同的 action type,即可完成更新。
最后,Preethi Kasireddy 结合了 Redux 的优势(纯净的 reducer 与 Immutable store)与 MobX 的优势(响应式),提出是否有 reducer 与 observable 相结合的可能,很有意思。
后记
在我看来,Redux 崇尚自由,小而美,在框架层面给予了开发者很大的空间,比如可以利用 middleware 机制去抽象业务逻辑。激发了大家造轮子的热情的同时,也引发了“全家桶”的吐槽,光异步请求就有 thunk、saga 等多种解决方案,令后来的开发者眼花缭乱。
MobX 则相对封闭,性能优势与代码量小,无法掩盖最佳实践的缺失。store 应该如何更好地组织?store 间通讯应该如何较好地处理?如何去做一些通用的抽象,比如 store 的复用?都需要我们去一一解决。
正如 Preethi Kasireddy 在演讲最后提到的,Redux 或是 MobX 的选择并不会意味着结束,或者说在现阶段下二者都有欠缺。一方面,我们可以通过封装 Redux 去降低开发成本,来适用自己的业务场景,提高工程效率,比如蚂蚁的dva,或是我所在团队即将开源的框架。亦或我们可以取长补短,MobX 胜在响应式带来的性能提高,避免不必要的重渲染,如果能与 Redux 的 middleware、reducer 结合,将会非常值得期待。