Mobx与Redux的异同

MobxRedux都是用来管理JavaScript应用的状态的解决方案,用以提供在某个地方保存状态、修改状态和更新状态,使我们的应用在状态与组件上解耦,我们可以从一个地方获得状态,在另一个地方修改,在其他地方得到他们更新后的状态。他们都遵循单一数据源的原则,这让我们更容易推断状态的值和状态的修改。当然他们并不一定要跟React绑定在一起,它们也可以在AngularJsVueJs这些框架库里使用。

描述

Redux作者说过,如果你不知道是否需要Redux,那就是不需要。在判断是否需要使用MobxRedux之前,我们首先需要知道他们究竟是要解决什么问题,以及当前是否遇到了这个问题。如今前端通常是要用组件components来构建一个应用,而组件中通常有自己的内部状态即state,但是随着应用越来越膨胀,组件自己内部维护的状态在膨胀的应用中很快会变得混乱。随着应用功能的不断拓展,通常会出现一些问题:

  • 一个组件通常需要和另一个组件共享状态。
  • 一个组件需要改变另一个组件的状态。
  • 组件层级太深,需要共享状态时状态要层层传递。
  • 子组件更新一个状态,可能有多个父组件,兄弟组件共用,实现困难。

这种情况下继续使用提取状态到父组件的方法你会发现很复杂,而且随着组件增多,嵌套层级加深,这个复杂度也越来越高。因为关联的状态多,传递复杂,很容易出现像某个组件莫名其妙的更新或者不更新的情况,异常排查也会困难重重。也就是说当应用膨胀到一定程度时,推算应用的状态将会变得越来越困难,此时整个应用就会变成一个有很多状态对象并且在组件层级上互相修改状态的混乱应用。在很多情况下,状态对象和状态的修改并没有必要绑定在一些组件上,我们可以尝试将其提升,通过组件树来得到与修改状态。
目前通常的解决方案是引入状态管理库,比如MobxReduxMobxRedux都是用来管理JavaScript应用的状态的解决方案,用以提供在某个地方保存状态、修改状态和更新状态,使我们的应用在状态与组件上解耦,我们可以从一个地方获得状态,在另一个地方修改,在其他地方得到他们更新后的状态。他们都遵循单一数据源的原则,这让我们更容易推断状态的值和状态的修改。当然他们并不一定要跟React绑定在一起,它们也可以在AngularJsVueJs这些框架库里使用。
ReduxMobx这类状态管理库一般都有附带的工具,例如在React中使用的有react-reduxmobx-react,他们使你的组件能够获得状态,一般情况下,这些组件被叫做容器组件container components,或者说的更加确切的话,就是连接组件connected components。通常只要将组件作为连接组件,就可以在组件层级的任何地方得到和更改状态。
对于MobxRedux的异同这个问题,是我最近在找实习的时候遇到的,分别为react mobxreact redux作简单的示例,文中的示例代码都在https://codesandbox.io/s/react-ts-template-forked-88t6in中。

Mobx

MobX是一个经过战火洗礼的库,他通过透明的函数响应式编程transparently applying functional reactive programming - TFRP使得状态管理变得简单和可扩展。MobX背后的哲学很简单: 任何源自应用状态的东西都应该自动地获得,其中包括UI、数据序列化等等,核心重点就是: MobX通过响应式编程实现简单高效,可扩展的状态管理。

// src/mobx-store/store.ts import { observable, action, makeAutoObservable } from "mobx"; class Store { constructor() { makeAutoObservable(this); } @observable state = { count: 1 }; @action setCount = (value: number) => { this.state.count = value; }; @action setCountIncrement = () => { this.state.count++; }; } export default new Store();
// src/counter-mobx.tsx import React from "react"; import { observer } from "mobx-react"; import store from "./mobx-store/store"; const CountMobx: React.FC = () => { return ( <div> <div>{store.state.count}</div> <button onClick={() => store.setCount(1)}>Set Count value 1</button> <button onClick={store.setCountIncrement}>Set Count Increment</button> </div> ); }; export default observer(CountMobx);

Redux

Redux用一个单独的常量状态树或者叫作对象保存这一整个应用的状态,这个对象不能直接被改变,当一些数据变化了,一个新的对象就会被创建,严格的单向数据流是Redux架构的设计核心。

// src/redux-store/store.ts import { createStore } from "redux"; const defaultState: State = { count: 1 }; export const actions = { SET_COUNT: "SET_COUNT" as const, SET_COUNT_INCREMENT: "SET_COUNT_INCREMENT" as const }; const reducer = (state: State = defaultState, action: Actions): State => { const { type } = action; switch (type) { case actions.SET_COUNT: { return { ...state, count: action.payload }; } case actions.SET_COUNT_INCREMENT: { return { ...state, count: state.count + 1 }; } default: return state; } }; export const store = createStore(reducer, defaultState); export interface State { count: number; } export type RootState = ReturnType<typeof store.getState>; export type AppDispatch = typeof store.dispatch; type SET_COUNT_INCREMENT = { type: typeof actions.SET_COUNT_INCREMENT; payload: void; }; type SET_COUNT = { type: typeof actions.SET_COUNT; payload: number; }; export type Actions = SET_COUNT_INCREMENT | SET_COUNT;
// src/counter-redux.tsx import React from "react"; import { AppDispatch, actions, State } from "./redux-store/store"; import { useSelector, useDispatch } from "react-redux"; const CountRedux: React.FC = () => { const count = useSelector((state: State) => state.count); const dispatch = useDispatch() as AppDispatch; return ( <div> <div>{count}</div> <button onClick={() => dispatch({ type: actions.SET_COUNT, payload: 1 })}> Set Count value 1 </button> <button onClick={() => dispatch({ type: actions.SET_COUNT_INCREMENT, payload: void 0 }) } > Set Count Increment </button> </div> ); }; export default CountRedux;
// src/App.tsx import React from "react"; import "./styles.css"; import CountMobx from "./counter-mobx"; import CountRedux from "./counter-redux"; import { Provider as ReduxProvider } from "react-redux"; import { store } from "./redux-store/store"; const App: React.FC = () => { return ( <div> <div>======Mobx======</div> <CountMobx /> <br /> <div>======Redux======</div> <ReduxProvider store={store}> <CountRedux /> </ReduxProvider> </div> ); }; export default App;

相同点

  • 为了解决状态管理混乱,无法有效的同步的问题,统一管理应用状态。
  • 一个状态只有一个可信的数据源,通常是以action的方式提供更新状态的途径。
  • 都带有状态与组件的链接管理库,例如react-reduxmobx-react

不同点

函数式和面向对象

  • Redux更多的是遵循函数式编程Functional Programming, FP思想,从数据上来说Redux理想的是immutableimmutable对象是不可直接赋值的对象,它可以有效的避免错误赋值的问题,例如reducer就是一个纯函数,对于相同的输入总是输出相同的结果。
  • Mobx则更多从面相对象Object Oriented Programming, OOP与响应式编程Reactive Programming角度考虑问题,从数据上说Mobx从始至终都是一份引用,这样可以使的Mobx的组件可以做到精准更新,将状态包装成可观察对象,一旦状态对象变更,就能自动获得更新。

store管理方式

  • Redux应用中通常将整个应用的state被储存在一棵object tree中,并且这个object tree只存在于唯一一个store中。
  • Mobx则通常按模块将应用状态划分,在多个独立的store中管理。

储存数据形式

  • Redux默认以JavaScript原生对象形式存储数据,这也就使得Redux需要手动追踪所有状态对象的变更。
  • Mobx使用可观察对象,通常是使用observable让数据的变化可以被观察,通过把属性转化成getter/setter来实现,当数据变更时将自动触发监听响应。

不可变和可变

  • Redux状态对象通常是不可变的Immutable,复制代码我们不能直接操作状态对象,而总是在原来状态对象基础上返回一个新的状态对象。
  • Mobx状态对象通常是可变的Mutable,可以直接使用新值更新状态对象。

状态调试

  • Redux提供进行时间回溯的开发工具,同时纯函数以及更少的抽象,让调试变得更加容易。
  • Mobx中有更多的抽象和封装,调试会相对比较困难,同时结果也相对难以预测。

最后

MobxRedux都是非常棒的两个库,使用上没有对错,只有合适不合适,只是可能需要在使用之前做好调研工作。或许有人需要减少编写的代码行数,那么就可能会提到Redux有太多的样板代码,而应该使用Mobx,可以减少xxx行代码。又或许有人需要更加明确的处理对象的变更,那么就可能感觉放弃Mobx的响应式魔法,而使用Redux去通过纯 JavaScript来推断与调试。又或许两个状态管理库并不冲突,可以同时存在,分别管理不同的模块的状态。

每日一题

https://github.com/WindrunnerMax/EveryDay

参考

https://cn.mobx.js.org/ https://www.redux.org.cn/docs/react-redux/ https://juejin.cn/post/6844903977553756168 https://juejin.cn/post/6924572729886638088 https://segmentfault.com/a/1190000011148981 https://www.cnblogs.com/tommymarc/p/15768138.html https://blog.csdn.net/leelxp/article/details/108450518 https://blog.csdn.net/Ed7zgeE9X/article/details/121896197 https://yangleiup.github.io/accumulate/redux%E4%B8%8Emobx%E5%8C%BA%E5%88%AB.html https://medium.com/@pie6k/better-way-to-create-type-safe-redux-actions-and-reducers-with-typescript-45386808c103