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

前言

当我们使用React开发web应用程序时,在React组件内,可以使用this.setState()this.state处理或访问组件内状态,但是随着项目变大,状态变复杂,通常需要考虑组件间通信问题,主要包括以下两点:

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

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

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

通常多组件需要处理同一状态,我们推荐将共享状态提升至他们的共同最近祖先组件内。更多详情查看

当项目越发复杂时,我们发现仅仅是提升状态已经无法适应如此复杂的状态管理了,程序状态变得比较难同步,操作,到处是回调,发布,订阅,这意味着我们需要更好的状态管理方式,于是就引入了状态管理库,如ReduxMobxJumpsuitAlt.js等。

状态管理

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

  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库。

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.

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

其原理如图:

  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.js

class 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.js

export 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

本文链接地址: 你需要Mobx还是Redux?

你需要Mobx还是Redux?的更多相关文章

  1. Mobx与Redux区别

    Mobx的实现思想和Vue几乎一样,所以其优点跟Vue也差不多:通过监听数据(对象.数组)的属性变化,可以通过直接在数据上更改就能触发UI的渲染,从而做到MVVM.响应式.上手成本低.开发效率高,在数 ...

  2. Mobx总结以及mobx和redux区别

    Mobx解决的问题 传统react使用的数据管理库为Redux.Redux要解决的问题是统一数据流,数据流完全可控并可追踪.要实现该目标,便需要进行相关的约束 Redux由此引出dispatch ac ...

  3. 几个月来使用mobx代替redux的一些总结

    遇到的一些小坑 React组件内部想要调用store里的action方法,得如下图,否则不会调用(这个现在看来好像不对,待重新检验) 而不能如下图 组件中调用改变store的action后,状态变化并 ...

  4. redux、immutablejs和mobx性能对比(三)

    四.我的结论 通过第三部分的数据数据分析,我觉得我们可以得到以下结论: 无论是在开发环境还是测试环下页面的首次加载速度结果都是:redux>immutablejs>mobx,但是他们之间的 ...

  5. 在react项目中使用redux or mobx?

    主要比较参数: 库体积,打包项目体积 开发体验 性能对比 在对比参数前首先分析一下redux和mobx的设计模式,redux和mobx都没有使用传统的mvc/mvvm形式,而且他们使用flux结构也略 ...

  6. 【译】Redux 还是 Mobx,让我来解决你的困惑!

    原文地址:Redux or MobX: An attempt to dissolve the Confusion 原文作者:rwieruch 我在去年大量的使用了 Redux,但我最近都在使用 Mob ...

  7. Redux/Mobx/Akita/Vuex对比 - 选择更适合低代码场景的状态管理方案

    近期准备开发一个数据分析 SDK,定位是作为数据中台向外输出数据分析能力的载体,前端的功能表现类似低代码平台的各种拖拉拽.作为中台能力的载体,SDK 未来很大概率会需要支持多种视图层框架,比如Vue2 ...

  8. 十分钟介绍mobx与react

    原文地址:https://mobxjs.github.io/mobx/getting-started.html 写在前面:本人英语水平有限,主要是写给自己看的,若有哪位同学看到了有问题的地方,请为我指 ...

  9. mobx源码解读1

    mobx是redux的代替品,其本身就是一个很好的MVVM框架.因此花点力气研究一下它. 网上下最新的2.75 function Todo() { this.id = Math.random() mo ...

随机推荐

  1. 数据库缓存mybatis,redis

    简介 处理并发问题的重点不在于你的设计是怎样的,而在于你要评估你的并发,并在并发范围内处理.你预估你的并发是多少,然后测试r+m是否支持.缓存的目的是为了应对普通对象数据库的读写限制,依托与nosql ...

  2. es6 for of 循环

    es6 新增了 for of 循环,只要继承了Iterator 接口的数据集合都可以使用 for of 去循环 for of 循环,统一数据集合的循环方法,解决了forEach循环的不能使用break ...

  3. eclipse建立工作集管理项目

    废话不多说,直接上图 然后新建java working set管理项目,让自己的项目清晰明了

  4. html 微信video放大后无法返回问题

    android  video播放视频放大后无法返回,先debug下debugx5.qq.com 发现用的不是X5内核 直接激活  debugmm.qq.com/?forcex5=true  问题解决 ...

  5. Linux硬盘文件分析取证(SSH过的IP)

    在线靶场: https://www.mozhe.cn 背景介绍 某运维人员发现服务器最近被一个IP连接过SSH,请找到连接服务器SSH的IP. 实训目标 1.了解Linux备份方式: 2.了解Acce ...

  6. Android应用跳转到微信和支付宝扫一扫

    新版的微信已经把微信功能的schema都禁掉了,意味着我们无法打开微信的扫一扫等功能,目前正常的只能先进入微信(我测试的时候是微信版本7.0.3)已经是很新的版本了 具体调起微信扫一扫代码如下,测试后 ...

  7. MPP-编码示例

    了解MPP的基本功能后,接下来具体分析编码的代码.首先把编码的代码提取出来,方便以后的使用. 完整的编码代码如下,相比较给出的示例代码,做了一些改动,输入的指令全部去除,将函数入口改为利用OpenCV ...

  8. Docker-镜像源加速配置

    Docker官网镜像服务在境外,拉取速度很比较慢,所以我们安装完Docker后一般会切换镜像源,将镜像源切换到国内以此达到加速的目的. 国内比较常用的Docker Hub镜像服务:1.阿里云2.Dao ...

  9. Dell服务器U盘安装Windows Server时识别不到硬盘

    Dell服务器U盘安装Windows Server时识别不到硬盘 1.下载驱动http://downloads.dell.com/FOLDER03688531M/1/SAS-RAID_Driver_T ...

  10. 使用jquery模拟请求,测试项目是否存在跨域限制

    1.Get 请求 <html> <head><script src="https://cdn.staticfile.org/jquery/1.10.2/jque ...