对于许多JavaScript开发人员来说,Redux最大的缺点是需要编写大量的模板代码来。一个更好的替代方案是MobX,它提供了类似的功能,但需要编写的代码更少。

本文的目标是帮助JavaScript开发人员判断两种状态管理解决方案中哪一种最适合目前的项目。

我将首先讨论使用MobX的优点和缺点,然后演示两个版本的之间的代码区别。

本文中项目的代码可以在GitHub上找到:

Redux和MobX有什么共同点?

首先,让我们看看他们都有共同点:

都是开源库
提供客户端状态管理

不受特定框架的约束
对React/React Native 框架有广泛的支持。

使用MobX的四个原因

现在让我们看看Redux和MobX之间的主要区别。

  1. 易于学习和使用

对于初学者,您可以在30分钟内学会如何使用MobX。一旦你具备了基本知识,就可以上手了。你不需要学习任何新东西。使用Redux,基础也很简单。然而,一旦您开始构建更复杂的应用程序,您将面临:

使用redx -thunk处理异步操作
使用redx -saga简化代码
定义处理计算值的选择器等。

使用MobX,所有这些情况都被“神奇地”解决了。您不需要额外的库来处理这些情况。

2.    编写的代码更少

要在Redux中实现一个功能,需要编写 reducers, actions, containers 和 components代码。如果做的是一个小项目,这尤其令人讨厌。而MobX只需要编写(store 和 components)。

3.   完全支持面向对象编程

如果您倾向于编写面向对象的代码,那么您可以使用OOP来使用MobX实现状态管理逻辑。通过@observable和@observer等装饰器,您可以轻松地使普通JavaScript components和stores成为响应式对象。如果您更喜欢函数式编程,没有问题——MobX也支持函数式编程。

另一方面,Redux主要采取面向函数式编程原则。但如果需要基于对象编程,可以使用redux-connect-decorator库。

4.    更轻松的处理嵌套数据

在大多数JavaScript应用程序中,您会发现自己在处理关联或嵌套数据。要想在Redux 中使用Store,必须首先对其进行规范化。需要写更多的代码来管理规范化数据中的引用对象。

在MobX中,建议以非规范化的形式存储数据。MobX可以为您跟踪关联数据的变化,并自动渲染更新。通过使用对象存储数据,可以直接引用在别的存储中定义的对象。此外,您可以使用(@)computed decoratorsmodifiers for observables来轻松解决复杂的数据带来的挑战。

三个不使用MobX的原因

1.  太自由

Redux是一个框架,它为如何编写状态代码提供了严格的指导。这意味着您可以轻松地编写测试并开发可维护的代码。MobX是一个库,没有特定的编程范式。这样做的危险在于,很容易采取捷径并应用可能导致代码不可维护的快速修复。

2.  调试困难

MobX的内部代码“神奇地”处理了大量逻辑,使您的应用程序具有响应性。在store和components之间有一个不可见的区域,当你遇到问题时,很难进行调试。如果您直接在组件中更改状态,而不使用@actions,将很难确定错误的来源。

3.MobX可能有更好的替代品

在软件开发中,新出现的趋势层出不穷。在短短的几年内,当前的软件技术可以迅速被淘汰。目前,有几种解决方案与Redux和Mobx竞争。例如Relay/Apollo 和 GraphQL, Alt.jsJumpsuit.。这些技术都有可能成为最受欢迎的技术。如果你真的想知道哪一种最适合你,你就必须全部试一试。

代码比较:Redux与MobX

说的够多了,让我们看看代码。

启动

redux版本:

在Redux中,我们首先定义我们的store,然后通过Provider将store传递给App。我们还需要定义redux-thunk和redux-promise-middleware来处理异步函数。redux-devtools-extension允许我们在调试模式下调试。

// src/store.js
import { applyMiddleware, createStore } from "redux";
import thunk from "redux-thunk";
import promise from "redux-promise-middleware";
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from "./reducers";

const middleware = composeWithDevTools(applyMiddleware(promise(), thunk));

export default createStore(rootReducer, middleware);

-------------------------------------------------------------------------------

// src/index.js
…
ReactDOM.render(
  <BrowserRouter>
    <Provider store={store}>
      <App />
    </Provider>
  </BrowserRouter>,
  document.getElementById('root')
);

MobX 版本:

在MobX中,我们需要设置多个store, 在本例中,我只使用了一个store,我将它放置在名为allStores的集合中。然后使用提供者将stores集合传递给应用程序。

正如前面提到的,MobX不需要外部库来处理异步操作。但是,我们确实需要mobx-remotedev来连接redux-devtools-extension调试工具。

// src/stores/index.js
import remotedev from 'mobx-remotedev';
import Store from './store';

const contactConfig = {
  name:'Contact Store',
  global: true,
  onlyActions:true,
  filters: {
    whitelist: /fetch|update|create|Event|entity|entities|handleErrors/
  }
};

const contactStore = new Store('api/contacts');

const allStores = {
  contactStore: remotedev(contactStore, contactConfig)
};

export default allStores;

-------------------------------------------------------------------------------

// src/index.js
…
ReactDOM.render(
  <BrowserRouter>
    <Provider stores={allStores}>
      <App />
    </Provider>
  </BrowserRouter>,
  document.getElementById('root')
);

两个版本初始化代码量相当,MboX相对少了一些import语句。

属性注入

Redux 版本:

在Redux中,store和action使用react-redux的connect()函数传递给props。

// src/pages/contact-form-page.js
…
  // accessing props
  <ContactForm
    contact={this.props.contact}
    loading={this.props.loading}
    onSubmit={this.submit}
  />
…

// function for injecting state into props
function mapStateToProps(state) {
  return {
    contact: state.contactStore.contact,
    errors: state.contactStore.errors
  }
}

// injecting both state and actions into props
export default connect(mapStateToProps, { newContact,
  saveContact,
  fetchContact,
  updateContact
})(ContactFormPage);

MobX 版本:

在MobX中,我们只需注入stores。我们在容器或组件类的顶部使用@inject来完成此操作。这使得store在props中可用,从而允许我们访问特定的store并将其传递给一个子组件。状态和操作都是通过store对象中的属性访问的,因此不需要像在Redux中那样分别传递它们。

// src/pages/contact-form-page.js

…
@inject("stores") @observer // injecting store into props
class ContactFormPage extends Component {
…
  // accessing store via props
  const { contactStore:store } = this.props.stores;
  return (
      <ContactForm
        store={store}
        form={this.form}
        contact={store.entity}
      />
  )
…
}

MobX版本似乎更容易阅读。但是,我们可以使用redux-connect-decorators来简化Redux代码。这样,MboX就失去了明显优势。

stores, actions, and reducers

下面展示一个用于一个action的示例。

Redux 版本:

Redux中需要定义reducer和actions

// src/actions/contact-actions.js
…
export function fetchContacts(){
  return dispatch => {
    dispatch({
      type: 'FETCH_CONTACTS',
      payload: client.get(url)
    })
  }
}
…

// src/reducers/contact-reducer
…
switch (action.type) {
    case 'FETCH_CONTACTS_FULFILLED': {
      return {
        ...state,
        contacts: action.payload.data.data || action.payload.data,
        loading: false,
        errors: {}
      }
    }

    case 'FETCH_CONTACTS_PENDING': {
      return {
        ...state,
        loading: true,
        errors: {}
      }
    }

    case 'FETCH_CONTACTS_REJECTED': {
      return {
        ...state,
        loading: false,
        errors: { global: action.payload.message }
      }
    }
}
…

MobX 版本:

在MobX中,action和reducer在一个模块中完成。我定义了一个异步操作,它在接收到response后调用另一个entities fetched 动作。

由于MobX使用OOP样式,这里定义的存储类已被重构,以便使用类构造函数轻松创建多个存储。因此,这里演示的代码是不绑定到特定域存储的基本代码。

// src/stores/store.js
…
@action
fetchAll = async() => {
  this.loading = true;
  this.errors = {};
  try {
    const response = await this.service.find({})
    runInAction('entities fetched', () => {
      this.entities = response.data;
      this.loading = false;
    });
  } catch(err) {
      this.handleErrors(err);
  }
}
…

在Redux中,我们编写了33行代码。在MobX中,我们使用了大约14行代码来实现相同的结果!

MobX版本的一个主要优点是,您可以在几乎所有store class中重用基本代码,几乎不需要修改。这意味着您可以更快地开发应用。

其他不同之处

为了在Redux中使用表单,可以使用redux-form. 。在MobX中,使用mobx-react-form。这两个库都很成熟,可以帮助您轻松地处理表单逻辑。

MobX的一个小缺点是,您不能直接访问可观察对象中的某些函数,因为它们不是真正的JavaScript对象。幸运的是,它们提供了toJS()函数,您可以使用该函数将可观察对象转换为纯JavaScript对象。

最后

显然,您可以看到MobX的代码库要精简得多。使用OOP风格和良好的开发实践,您可以快速构建应用程序。主要缺点是很容易编写丑陋的、不可维护的代码。

另一方面,Redux更受欢迎,非常适合构建大型复杂项目。它是一个严格的框架,具有确保每个开发人员编写易于测试和维护的代码的安全措施。但是,它并不适合小型项目。

尽管MobX有一些缺点,但是如果遵循良好的实践,仍然可以构建大型项目。

使一切尽可能的简单,但不要太简单

本文章花了大量的篇幅来说明是继续使用Redux还是迁移到MobX。最终的决定权取决于项目的类型,以及可用的资源。