jerytang

21 天前

react 的数据管理方案:redux 还是 mobx?

本文作者:IMWeb jerytang 原文出处:IMWeb社区 未经同意,禁止转载

mobx 简介

和 redux 类似,mobx 是一个数据管理库,都可以和 react 配合使用。它区别于 redux 的最大特点是,可以直接修改数据,对 UI 进行精确刷新。精确更新是什么意思呢,看下面的例子。

import { observable } from 'mobx';

const obj = observable({
    a: 1,
    b: 2
})

autoRun(() => {
    console.log(obj.a)
})

obj.b = 3 // 什么都没有发生
obj.a = 2 // observe 函数的回调触发了,控制台输出:2
  • autoRun 中的函数对 a 进行了取值 get 操作,obj.a 和所在的函数完成了绑定关系;
  • 直接对 obj.a 进行赋值 set 操作,触发了 get 操作所在的函数执行;
  • 对 b 的操作没有触发——mobx 是精确到字段更新

将 mobx 的数据管理能力应用到 react 中: React Component 对数据源字段进行精确响应更新。

mobx 的更多介绍,移步mobxjs官网

这里直接上实际例子。

mobx 的例子是辅导题库项目中实践方案的简化演示。

实际例子:mobx 和 redux 对比

实现一个计数器增加、减少的功能。

00

如果直接使用 setState 也很容易实现这个功能。但是,这里分别用redux方案 和 mobx方案 实现上面的功能。

为了演示方便,将所有的代码都放在一个文件中。查看 mobx 实现的代码前,先了解下装饰器(decorator)是什么

redux 方案代码

import React, { Component } from 'react';
import {
  createStore,
  bindActionCreators,
} from 'redux';
import { Provider, connect } from 'react-redux';

// ①action types
const COUNTER_ADD = 'counter_add';
const COUNTER_DEC = 'counter_dec';

const initialState = {a: 0};
// ②reducers
function reducers(state = initialState, action) {
  switch (action.type) {
  case COUNTER_ADD:
    return {...state, a: state.a+1};
  case COUNTER_DEC:
    return {...state, a: state.a-1};
  default:
    return state
  }
}

// ③action creator
const incA = () => ({ type: COUNTER_ADD });
const decA = () => ({ type: COUNTER_DEC });
const Actions = {incA, decA};

class Demo extends Component {
  render() {
    const { store, actions } = this.props;
    return (
      <div>
        <p>a = {store.a}</p>
        <p>
          <button className="ui-btn" onClick={actions.incA}>增加 a</button>
          <button className="ui-btn" onClick={actions.decA}>减少 a</button>
        </p>
      </div>
    );
  }
}

// ④将state、actions 映射到组件 props
const mapStateToProps = state => ({store: state});
const mapDispatchToProps = dispatch => ({
  // ⑤bindActionCreators 简化 dispatch
  actions: bindActionCreators(Actions, dispatch)
})
// ⑥connect产生容器组件
const Root = connect(
  mapStateToProps,
  mapDispatchToProps
)(Demo)

const store = createStore(reducers)
export default class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <Root />
      </Provider>
    )
  }
}

mobx 方案代码

import React, { Component } from 'react';
import { observable, action } from 'mobx';
import { Provider, observer, inject } from 'mobx-react';

// 定义数据结构
class Store {
  // ① 使用 observable decorator 
  @observable a = 0;
}

// 定义对数据的操作
class Actions {
  constructor({store}) {
    this.store = store;
  }
  // ② 使用 action decorator 
  @action
  incA = () => {
    this.store.a++;
  }
  @action
  decA = () => {
    this.store.a--;
  }
}

// ③实例化单一数据源
const store = new Store();
// ④实例化 actions,并且和 store 进行关联
const actions = new Actions({store});

// inject 向业务组件注入 store,actions,和 Provider 配合使用
// ⑤ 使用 inject decorator 和 observer decorator
@inject('store', 'actions')
@observer
class Demo extends Component {
  render() {
    const { store, actions } = this.props;
    return (
      <div>
        <p>a = {store.a}</p>
        <p>
          <button className="ui-btn" onClick={actions.incA}>增加 a</button>
          <button className="ui-btn" onClick={actions.decA}>减少 a</button>
        </p>
      </div>
    );
  }
}

class App extends Component {
  render() {
    // ⑥使用Provider 在被 inject 的子组件里,可以通过 props.store props.actions 访问
    return (
      <Provider store={store} actions={actions}>
        <Demo />
      </Provider>
    )
  }
}

export default App;

使用 mobx 时,借鉴了 redux 架构的优点:

  1. 单一数据源,这样避免了子组件、父组件状态同步的问题
  2. 可以做到让组件无状态化
  3. 使用 Provider 注入,让 store actions 可以在子组件中,通过 props 访问使用

下面是一些不同点:

  1. mobx 使用的是 @inject 装饰器语法注入,redux 使用的是 connect 语法注入
  2. mobx 使用 @observer 语法,让一个 component 能响应 store 字段更新
  3. mobx 会动态精确绑定数据字段和对应 component 关系, redux 使用 connect 参数手动控制传递哪些字段
  4. mobx 直接修改 store 的状态,但是必须在 @action 修饰的函数中完成,@action 的语义,表示这是一个修改状态的操作
  5. redux Provider 传递 store 是强约定,mobx Provider 灵活传递 store actions,也可以是其它名字,比如 db
  6. redux 使用了比较难以理解的高阶函数和参数 connect combineReducers bindActionCreators mapStateToProps mapDispatchToProps ,mobx 方案,除了使用 decorator 语法,没有其它让人感觉理解困难的函数。
  7. redux 引入了数据流,mobx 没有数据流的概念,通过 actions 直接改变数据

编码工作量对比

代码功能少,感觉不到差别,好像就是 redux 方案有点难理解;而 mobx 比较直接,也比较 magic,就像当年刚开始使用 jQuery 的感觉。

现在给计数器增加乘以 2 倍的功能。

double

看下 两个方案的代码修改量差异

redux

01

mobx

02

两者都要在界面上做调整。

  • redux 方案需要要改 3 个地方: action_types、action_creator 、reducer
  • mobx 方案需要改 1 个地方: 添加一个 action

实现同样功能,redux 需要关注的地方多了 2/3。功能比较少的时候,感觉不到工作量差异多大。但是如果有个 500 个 action 要处理,这时候工作量的差距就是按时完成和加班也做不完的差别了。

这里是增加功能,同样,删除功能,也要删除更多的地方,改动更多的文件。

在实际项目中,action_types、action_creator 、reducer 分布于不同的文件夹,需要来回切换文件修改。

随着功能逐渐增加,redxu 方案,用一个 reducer 来处理,可能就不合适了,需要对 reducers 进行了拆分;mobx 方案也面临类似的问题,Actions、Store 类会越来越大。

如何扩展呢?

mobx 在大项目中的扩展能力

redux 方案,本质上还是通过添加更多的 switch 语句来实现扩展,将 store 分支节点的 reducer 分散到不同的文件,再通过工具函数combineReducers 合并成一个 rootReducer。

mobx 方案的扩展非常简单,需要扩展 store 和 actions。并且,actions 和 store 的扩展方式完全一致,通过给父类添加成员:

extendable

03

结论

为了不加班,我站 mobx 这边。

参考

0条评论

    您需要 注册 一个IMWeb账号或者 才能进行评论。