你须要Mobx仍是Redux?

在过去一年,愈来愈多的项目继续或者开始使用React和Redux开发,这是目前前端业内很广泛的一种前端项目解决方案,可是随着开发项目愈来愈多,愈来愈多样化时,我的又有了不一样的感觉和想法。是否是由于已经有了一个比较广泛的,熟悉的项目技术栈,咱们就一直彻底沿用呢,有没有比他更适合的方案呢?恰逢团队最近有一个新项目,因而博主开始思考,有没有可能使用其余可替代技术开发呢?既能提升开发效率,又能拓展技术储备和眼界,通过调研,选择了Mobx,最终使用React+Mobx搭建了新项目,本篇总结分享从技术选型到项目实现的较完整过程,但愿共同进步。javascript

欢迎访问个人我的博客html

前言

当咱们使用React开发web应用程序时,在React组件内,可使用this.setState()this.state处理或访问组件内状态,可是随着项目变大,状态变复杂,一般须要考虑组件间通讯问题,主要包括如下两点:前端

  1. 某一个状态须要在多个组件间共享(访问,更新);
  2. 某组件内交互须要触发其余组件的状态更新;

关于这些问题,React组件开发实践推荐将公用组件状态提高:java

Often, several components need to reflect the same changing data. We recommend lifting the shared state up to their closest common ancestorreact

一般多组件须要处理同一状态,咱们推荐将共享状态提高至他们的共同最近祖先组件内。更多详情查看web

当项目愈加复杂时,咱们发现仅仅是提高状态已经没法适应如此复杂的状态管理了,程序状态变得比较难同步,操做,处处是回调,发布,订阅,这意味着咱们须要更好的状态管理方式,因而就引入了状态管理库,如ReduxMobxJumpsuitAlt.js等。编程

状态管理

状态管理库,不管是Redux,仍是Mobx这些,其本质都是为了解决状态管理混乱,没法有效同步的问题,它们都支持:redux

  1. 统一维护管理应用状态;
  2. 某一状态只有一个可信数据来源(一般命名为store,指状态容器);
  3. 操做更新状态方式统一,而且可控(一般以action方式提供更新状态的途径);
  4. 支持将store与React组件链接,如react-reduxmobx-react;一般使用状态管理库后,咱们将React组件从业务上划分为两类:
    1. 容器组件(Container Components):负责处理具体业务和状态数据,将业务或状态处理函数传入展现型组件;
    2. 展现型组件(Presentation Components):负责展现视图,视图交互回调内调用传入的处理函数;

Mobx VS Redux

目前来看,Redux已经是React应用状态管理库中的霸主了,而Mobx则是一方诸侯,咱们为何要选择Mobx,而不是继续沿用Redux呢,那就须要比较他们的异同了。后端

Mobx和Redux都是JavaScript应用状态管理库,都适用于React,Angular,VueJs等框架或库,而不是局限于某一特定UI库。react-router

Redux

要介绍Redux,咱们就不得不谈到Flux了:

Flux is the application architecture that Facebook uses for building client-side web applications.It's more of a pattern rather than a formal framework

Flux是Facebook用来开发客户端-服务端web应用程序的应用架构,它更可能是一种架构模式,而非一个特定框架。详解Flux

而Redux更多的是遵循Flux模式的一种实现,是一个JavaScript库,它关注点主要是如下几方面:

  1. Action:一个JavaScript对象,描述动做相关信息,主要包含type属性和payload属性:
    1. type:action 类型;
    2. payload:负载数据;
  2. Reducer:定义应用状态如何响应不一样动做(action),如何更新状态;
  3. Store:管理action和reducer及其关系的对象,主要提供如下功能:
    1. 维护应用状态并支持访问状态(getState());
    2. 支持监听action的分发,更新状态(dispatch(action));
    3. 支持订阅store的变动(subscribe(listener));
  4. 异步流:因为Redux全部对store状态的变动,都应该经过action触发,异步任务(一般都是业务或获取数据任务)也不例外,而为了避免将业务或数据相关的任务混入React组件中,就须要使用其余框架配合管理异步任务流程,如redux-thunkredux-saga等;

Mobx

Mobx是一个透明函数响应式编程(Transparently Functional Reactive Programming,TFRP)的状态管理库,它使得状态管理简单可伸缩:

Anything that can be derived from the application state, should be derived. Automatically.

任何起源于应用状态的数据应该自动获取。

其原理如图:

Mobx Philosophy

  1. Action:定义改变状态的动做函数,包括如何变动状态;

  2. Store:集中管理模块状态(State)和动做(action);

  3. Derivation(衍生):从应用状态中派生而出,且没有任何其余影响的数据,咱们称为derivation(衍生),衍生在如下状况下存在:

    1. 用户界面;

    2. 衍生数据;

      衍生主要有两种:

      1. Computed Values(计算值):计算值老是可使用纯函数(pure function)从当前可观察状态中获取;
      2. Reactions(反应):反应指状态变动时须要自动发生的反作用,这种状况下,咱们须要实现其读写操做;
import {observable, autorun} from 'mobx';

var todoStore = observable({
    /* some observable state */
    todos: [],

    /* a derived value */
    get completedCount() {
        return this.todos.filter(todo => todo.completed).length;
    }
});

/* a function that observes the state */
autorun(function() {
    console.log("Completed %d of %d items",
        todoStore.completedCount,
        todoStore.todos.length
    );
});

/* ..and some actions that modify the state */
todoStore.todos[0] = {
    title: "Take a walk",
    completed: false
};
// -> synchronously prints: 'Completed 0 of 1 items'

todoStore.todos[0].completed = true;
// -> synchronously prints: 'Completed 1 of 1 items'
复制代码

函数式和面向对象

Redux更多的是遵循函数式编程(Functional Programming, FP)思想,而Mobx则更多从面相对象角度考虑问题。

Redux提倡编写函数式代码,如reducer就是一个纯函数(pure function),以下:

(state, action) => {
  return Object.assign({}, state, {
    ...
  })
}
复制代码

纯函数,接受输入,而后输出结果,除此以外不会有任何影响,也包括不会影响接收的参数;对于相同的输入老是输出相同的结果。

Mobx设计更多偏向于面向对象编程(OOP)和响应式编程(Reactive Programming),一般将状态包装成可观察对象,因而咱们就可使用可观察对象的全部能力,一旦状态对象变动,就能自动得到更新。

单一store和多store

store是应用管理数据的地方,在Redux应用中,咱们老是将全部共享的应用数据集中在一个大的store中,而Mobx则一般按模块将应用状态划分,在多个独立的store中管理。

JavaScript对象和可观察对象

Redux默认以JavaScript原生对象形式存储数据,而Mobx使用可观察对象:

  1. Redux须要手动追踪全部状态对象的变动;
  2. Mobx中能够监听可观察对象,当其变动时将自动触发监听;

不可变(Immutable)和可变(Mutable)

Redux状态对象一般是不可变的(Immutable):

switch (action.type) {
  case REQUEST_POST:
  	return Object.assign({}, state, {
      post: action.payload.post
  	});
  default:
    retur nstate;
}
复制代码

咱们不能直接操做状态对象,而老是在原来状态对象基础上返回一个新的状态对象,这样就能很方便的返回应用上一状态;而Mobx中能够直接使用新值更新状态对象。

mobx-react和react-redux

使用Redux和React应用链接时,须要使用react-redux提供的Providerconnect

  1. Provider:负责将Store注入React应用;
  2. connect:负责将store state注入容器组件,并选择特定状态做为容器组件props传递;

对于Mobx而言,一样须要两个步骤:

  1. Provider:使用mobx-react提供的Provider将全部stores注入应用;
  2. 使用inject将特定store注入某组件,store能够传递状态或action;而后使用observer保证组件能响应store中的可观察对象(observable)变动,即store更新,组件视图响应式更新。

选择Mobx的缘由

  1. 学习成本少:Mobx基础知识很简单,学习了半小时官方文档和示例代码就搭建了新项目实例;而Redux确较繁琐,流程较多,须要配置,建立store,编写reducer,action,若是涉及异步任务,还须要引入redux-thunkredux-saga编写额外代码,Mobx流程相比就简单不少,而且不须要额外异步处理库;
  2. 面向对象编程:Mobx支持面向对象编程,咱们可使用@observable and @observer,以面向对象编程方式使得JavaScript对象具备响应式能力;而Redux最推荐遵循函数式编程,固然Mobx也支持函数式编程;
  3. 模版代码少:相对于Redux的各类模版代码,如,actionCreater,reducer,saga/thunk等,Mobx则不须要编写这类模板代码;

不选择Mobx的可能缘由

  1. 过于自由:Mobx提供的约定及模版代码不多,这致使开发代码编写很自由,若是不作一些约定,比较容易致使团队代码风格不统一,因此当团队成员较多时,确实须要添加一些约定;
  2. 可拓展,可维护性:也许你会担忧Mobx能不能适应后期项目发展壮大呢?确实Mobx更适合用在中小型项目中,但这并不表示其不能支撑大型项目,关键在于大型项目一般须要特别注意可拓展性,可维护性,相比而言,规范的Redux更有优点,而Mobx更自由,须要咱们本身制定一些规则来确保项目后期拓展,维护难易程度;

代码对比

接下来咱们使用Redux和Mobx简单实现同一应用,对比其代码,看看它们各自有什么表现。

架构

在Redux应用中,咱们首先须要配置,建立store,并使用redux-thunkredux-saga中间件以支持异步action,而后使用Provider将store注入应用:

// src/store.js
import { applyMiddleware, createStore } from "redux";
import createSagaMiddleware from 'redux-saga'
import React from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from "./reducers";
import App from './containers/App/';

const sagaMiddleware = createSagaMiddleware()
const middleware = composeWithDevTools(applyMiddleware(sagaMiddleware));

export default createStore(rootReducer, middleware);

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

Mobx应用则能够直接将全部store注入应用:

import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'mobx-react';
import { BrowserRouter } from 'react-router-dom';
import { useStrict } from 'mobx';
import App from './containers/App/';
import * as stores from './flux/index';

// set strict mode for mobx
// must change store through action
useStrict(true);

render(
  <Provider {...stores}> <BrowserRouter> <App /> </BrowserRouter> </Provider>,
  document.getElementById('app')
);
复制代码

注入Props

Redux:

// src/containers/Company.jsclass CompanyContainer extends Component {
  componentDidMount () {
    this.props.loadData({});
  }
  render () {
    return <Company infos={this.props.infos} loading={this.props.loading} /> } } … // function for injecting state into props const mapStateToProps = (state) => { return { infos: state.companyStore.infos, loading: state.companyStore.loading } } const mapDispatchToProps = dispatch => { return bindActionCreators({ loadData: loadData }, dispatch); } // injecting both state and actions into props export default connect(mapStateToProps, { loadData })(CompanyContainer); 复制代码

Mobx:

@inject('companyStore')
@observer
class CompanyContainer extends Component {
  componentDidMount () {
    this.props.companyStore.loadData({});
  }
  render () {
    const { infos, loading } = this.props.companyStore;
    return <Company infos={infos} loading={loading} /> } } 复制代码

定义Action/Reducer等

Redux:

// src/flux/Company/action.jsexport function fetchContacts(){
  return dispatch => {
    dispatch({
      type: 'FREQUEST_COMPANY_INFO',
      payload: {}
    })
  }
}
…

// src/flux/Company/reducer.js
const initialState = {};
function reducer (state = initialState, action) {
  switch (action.type) {
    case 'FREQUEST_COMPANY_INFO': {
      return {
        ...state,
        contacts: action.payload.data.data || action.payload.data,
        loading: false
      }
    }
    default:
      return state;
  }
}
复制代码

Mobx:

// src/flux/Company/store.js
import { observable, action } from 'mobx';

class CompanyStore {
  constructor () {
    @observable infos = observable.map(infosModel);
  }

  @action
  loadData = async(params) => {
    this.loading = true;
    this.errors = {};
    return this.$fetchBasicInfo(params).then(action(({ data }) => {
      let basicInfo = data.data;
      const preCompanyInfo = this.infos.get('companyInfo');
      this.infos.set('companyInfo', Object.assign(preCompanyInfo, basicInfo));
      return basicInfo;
    }));
  }

  $fetchBasicInfo (params) {
    return fetch({
      ...API.getBasicInfo,
      data: params
    });
  }
}
export default new CompanyStore();
复制代码

异步Action

若是使用Redux,咱们须要另外添加redux-thunkredux-saga以支持异步action,这就须要另外添加配置并编写模板代码,而Mobx并不须要额外配置。

redux-saga主要代码有:

// src/flux/Company/saga.js
// Sagas
// ------------------------------------
const $fetchBasicInfo = (params) => {
  return fetch({
    ...API.getBasicInfo,
    data: params
  });
}

export function *fetchCompanyInfoSaga (type, body) {
  while (true) {
    const { payload } = yield take(REQUEST_COMPANY_INFO)
    console.log('payload:', payload)
    const data = yield call($fetchBasicInfo, payload)
    yield put(receiveCompanyInfo(data))
  }
}
export const sagas = [
  fetchCompanyInfoSaga
];
复制代码

一些想法

不管前端仍是后端,遇到问题,大多数时候也许你们老是习惯于推荐已经广泛推广使用的,习惯,熟悉就很容易变成瓜熟蒂落的,咱们应该更进一步去思考,合适的才是更好的。

固然对于“Redux更规范,更靠谱,应该使用Redux”或"Redux模版太多,太复杂了,应该选择Mobx"这类推断,咱们也应该避免,这些都是相对而言,每一个框架都有各自的实现,特点,及其适用场景,正好比Redux流程更复杂,但熟悉流程后就更能把握它的一些基础/核心理念,使用起来可能更有心得及感悟;而Mobx简单化,把大部分东西隐藏起来,若是不去特别研究就不能接触到它的核心/基本思想,也许使得开发者一直停留在使用层次。

因此不管是技术栈仍是框架。类库,并无绝对的比较咱们就应该选择什么,抛弃什么,咱们应该更关注它们解决什么问题,它们解决问题的关注点,或者说实现方式是什么,它们的优缺点还有什么,哪个更适合当前项目,以及项目将来发展。

参考

  1. An in-depth explanation of Mobx
  2. Redux & Mobx
  3. Mobx
  4. Redux vs Mobx
相关文章
相关标签/搜索