redux-mobx

redux&mobx对比

image

前言

redux和mobx都是javascript的状态管理库,我们团队一直都在用它与react一起使用,我一直都在用redux,各种项目,各种用法。redux + thunk, redux + saga,或是引入immutable。我最近在一个后台内部使用的项目上尝试了mobx,由于有redux的使用基础,询问了下用过的同事,传授了几句话,甚至没看mobx的文档就开始使用了,可见redux和mobx从使用上这方面讲,存在很多的相同点。可能你看过很多的文章有关于这方面的,我写的也与他们别无二至,我的观点就是mobx也好,redux也好,他们各有优缺点,用哪一个都没有错,即使你的项目越来越大,mobx也完全可以胜任。

主要内容

  • 函数式编程,面向对象编程和响应式编程
  • 简单讲下redux,着重讲下mobx
  • 对比redux&mobx优缺点
  • 使用mobx时的心得体会

函数式编程,面向对象编程和响应式编程

之所以讲这个,是因为redux是遵循函数式编程思想,例如reducer通过一个个的纯函数把旧的state生成新的state,而不是改变旧的state,这就存在函数式编程的思想。又引出了一个概念纯函数,所谓纯函数就是同样的输入,同样的输出。举个例子:

1
2
3
4
5
function add (a, b) {
return a + b;
}

add(1, 2); // 3

最简单的一个例子,add函数在输入1,2的参数情况下,始终会输出3。不会出现任何所谓的副作用,也就是影响到其他的地方,例如全局变量。
ok,有一个问题,

redux为什么要用纯函数来处理state?

reducer将旧的状态(prev)和要修改的数据一起传进去,然后返回一个新的(next)状态,prev和next相比较来确定storge数据是否改变。如果我们用不纯的函数,prev和next将一致,就算数据改变,hasChanged也会是false。

面向对象编程

你可能常常听到面向对象有三大特性:封装,继承,多态。这里不做过多阐述。

说下面向对象编程产生的原因:由于面向过程在构造系统时,无法解决
重用,维护,扩展
的问题,代码逻辑过于复杂,代码晦涩难懂,因此人们开始想能不能让计算机直接模拟现实的环境,以人类解决问题的方法,思路,习惯和步骤来设计相应的应用程序。于是,面向对象的编程思想就产生了。

面向对象编程最重要的就是类,类是创建对象的模版,一个类可以创建多个对象。mobx的store就是一个个类,这里的类只创造了一个对象,多个类的实例化也就是多个store。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { observable, action } from 'mobx';

class IndexStore {
@observable searchField = 1;
...
}

class otherStore {
@observable searchField = 1;
...
}

export default {
index: new IndexStore(),
other: new otherStore()
};

响应式编程

这里我看到一个很好的比喻

老张还喝水,那么煮开水
有两把水壶,一个是普通水壶,一个是响水壶.

  1. 老张把水壶放到火上,在旁边等待着水开。(同步阻塞)
    老张觉得自己有点傻
  2. 于是老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没开(同步非阻塞)
    老张还是觉得有点傻,于是乎换了水壶
  3. 这回用的是响水壶,老张继续在旁边等着响。(异步阻塞)
    老张觉得这样不用等啊,听声就好了
  4. 于是老张去客厅看电视了,响了再去看水壶(异步非阻塞)
    老张心满意足,很是傲娇

回归到本质回答这个问题:响应式编程,本质上是对数据流或某种变化所作出的反应,但是这个变化什么时候发生是未知的,所以他是一种基于异步、回调的方式在处理问题。

那么mobx在哪里用到了这种思想呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var globalID = 0
function observable(obj) {
var oID = ++globalID
return new Proxy(obj, {
get: function (target, key, receiver) {
collect.startCollect(oID + '' +key)
return Reflect.get(target, key, receiver)
},
set: function (target, key, value, receiver) {
Reflect.set(target, key, value, receiver)
collection[oID + '' + key] && collection[oID + '' + key].forEach(c => {
c()
});
}
})
}

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
这里用Proxy是因为当你在可观察对象上加入新的属性时,就不会像Object.defineProperty那样新的属性没法监听了。

redux (react)

image
这里不讲怎么使用react-redux,谈谈redux的优点和缺点

优点

  • redux的优点在SPA应用中最有体现,当然分页也可以用,redux解决兄弟组件间通信,只要connect一个组件,这个组件无论在任何地方都能拿到全局的state(redux的state)
  • redux让应用的状态变化变得更加可预测,必须通过action改变state
  • redux与很多中间件结合,搭配immutable使应用的性能提升,开发效率提高。

缺点

  • store给我的感觉是在写全局变量,当一个复杂大型的项目多人一起开发时,命名就有可能出现重复,本来想更改这个模块的state,存在相互影响的问题,所以一般大家会对自己的模块增加命名空间解决此问题
  • 为了配合函数式编程,不可变思想,导致存在大量的模版代码,这个虽然说不上是缺点,但是开发起来就很头痛,所以也衍生出很多代码来自动生成模版。

这些缺点也让作者出现’打脸式’回答:’You might not need Redux’,’try mobx’,其实就是应用场景比较单一,不能覆盖多种场景导致的。正所谓你之蜜糖,我之砒霜。

mobx(react)

image

从图中看出和redux对比,就是不用redux更改store,而是actions直接修改sotre了,因为mobx的原理是通过观察者模式对数据做出追踪处理,在对可观察属性的作出变更或者引用的时候,触发其依赖的监听函数。
什么意思呢?像redux的数据是不可变的,如果你理解不可变的话。它需要更改数据的引用来感知数据的变化,而mobx不会更改数据的引用,是通过更改值,监测值的变化进而重新渲染等工作。

store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import {
observable,
action,
configure,
computed,
runInAction
} from 'mobx';

configure({ enforceActions: true });
// useStrict(true); mobx4之前

class Store {
@observable id = 1;
@observable data = [];

@action.bound
@action('find data')
async findData() {
try {
const res = await axios({
method: 'POST',
url: '/metadata/api/data',
data: {
id: this.id
}
});
runInAction(() => {
this.data = res.data || [];
});
} catch (e) {
console.log(e);
}
@computed
get pageCount() {
return Math.ceil(this.totalCount / this.pageSize);
}
}
}

export default {
store: new Store()
};

entry

1
2
3
4
5
6
7
8
ReactDOM.render(
<Provider {...store}>
<Frame {...options}>
<Entry />
</Frame>
</Provider>,
document.getElementById('app')
);

component

1
2
3
4
5
@inject('store')
@observer
export default class Page extends Component {
....
}

pou出了一段mobx我自己的用法,可以看出mobx没有redux中的Actions、Action Creator、 Action Types、Reducer、Global Store,一个Store把所有事都干了,在 Mobx 中可以使用 inject 获得 store 依赖。然后 store 可以传递 substate 和 actions 给组件。Mobx 的 observer 确保组件在 store 中 observable 的属性变化时更新。

mobx对比redux

学习难度

有人说mobx简单,redux较难,是因为redux延伸出的东西太多,mobx把这些封装到一起,单说redux这个抽象的理念比较难理解,再言之,我认为两种语言的学习难度都不能作为是否选择这个状态管理库的先决条件,作为一个优秀的开发者,两者还是都要学习了解的。

性能

在写redux的action的时候,总是需要用到扩展语句或者Object.assign()的方式来得到一个新的state,这一点对于JavaScript而言是对象的浅拷贝,它对内存的开销肯定是大于mobX中那样直接操作对象属性的方式大得多。
但是对于现在的浏览器和硬件的性能来说一般的应用都不会遇到这种性能的问题。

适用场景

redux适用大型复杂多人开发的项目,是因为redux的特性是单向的数据流,任何操作和行为的结果都是可预测的,易测试的。而mobx也是适用较复杂的,如果太简单的话直接setState的就好了,mobx相对redux显得有些灵活,所以如果要在大型项目用的化,还是团队内部约定规范,使项目变得易维护。

开发体验

我个人认为mobx的开发体验简直不要太爽,不需要构思reducer如何把一个旧的state换成新的state,可能一个不小心就把一个key的值覆盖掉,mobx而是直接赋值。没有冗余的actions模版代码,更不存在命名空间问题,相信无论哪个开发者用过mobx和redux之后都会认为mobx的开发舒适度更高一些。

redux更像是手动挡的车,mobx更像是自动挡的。

心得&&最佳实践

最近做的项目基于mobx + react系写的,回头看了一下项目,一些地方并没有遵循最佳实践的写法,而是有种redux的思想写mobx的感觉。
官方介绍建议新建两个store(实际上应该是两种),一个UI state一个domain state

  • UI state是指当前UI的状态,比如:窗口尺寸、当前展示的页面、渲染状态、网络状态等等
  • Domain state则主要包含页面所需的各种数据(一般是需要从后端获取的)。例如:
    • 文章详情(id为索引的数据表)
    • 首页feed(只有一个,不需要列表)
    • 推荐列表(推荐id索引的数据表,每一项的内容又是一个文章id的列表)

redux + PureComponent + immutable是提升性能的最佳实践
而mobx自己做了这些事情。
也导致了mobx在双向绑定的同时禁掉了react自身的刷新

PureComponent 有很多坑,这里就不一一列举了,网上有很多关于PureComponent的最佳用法。

redux改变值的方式是通过拷贝原来的对象生成新的对象,从而触发组件的componentWillReceiveProps,而MobX改变值只是在原始值的基础上改变,所以值的引用是没有改变的,这也就导致使MobX不会触发componentWillReceiveProps。

基于这种原因所以mobx-react提供了componentWillReact来触发MobX值的改变,但是它不只是监听MobX值的改变,同时包含componentReceiveProps的功能,所以在使用MobX之后,并不需要componentWillReceiveProps方法了。