解读 Mobx 及与 redux 的对比
众所周知,在 react 生态中,状态管理工具除了 redux,比较常用的就是 mobx 了。所以今天将会初略的写一下mobx的用法,总结mobx的使用场景(技术选型是一件比较有挑战的事,需要根据实际的应用场景来决定)。
mobx 的 API 非常的简洁,需要了解的内容并不多,如果在 react 中使用,需要借助 mobx-react。
首先在项目中安装 mobx 和 mobx-react:
npm i --save-D mobx mobx-react
mobx 中会大量使用装饰器,如果使用ES,可以借助babel来使用ES7中的装饰器特性;这里更推荐Typescript,可以使用"experimentalDecorators": true来开启对装饰器的支持,同时借助TSX,在react使用Typescript体验会非常的棒。
mobx 的流程图如上,通常是:触发 action,在 action 中修改 state,通过computed拿到state的计算值,自动触发对应的reactions,这里包含autorun,渲染视图等。有一点需要注意:相对于react来说,mobx没有一个全局的状态树,状态分散在各个独立的store中。mobx的工作原理非常简单,使用Object.defineProperty来拦截对数据的访问,一旦值发生变化,将会调用react的render方法来实现重新渲染视图的功能或者触发autorun等。
下面看几个示例,我会尽量在示例代码中包含多个概念(同时这里使用了 Typescript,如果不熟悉,可以看一下 这个),下面是第一个例子:
import * as React from 'react';
import { observable, action, autorun, when, computed } from 'mobx';
import { observer } from 'mobx-react';
@observer
export default class Item extends React.Component {
disposer: () => void;
@observable
count: string = 'right';
@action
setCount = () => {
this.count = this.count === 'right' ? 'wrong' : 'right';
}
cancelAutoRun = () => {
if (this.disposer) {
this.disposer();
}
}
@computed
get helloCount() {
return `hello ${this.count}`;
}
render() {
return(
<div>
<h3>{this.count}</h3>
<h4>{this.helloCount}</h4>
<button onClick={this.setCount}>click</button>
<button onClick={this.cancelAutoRun}>cancel</button>
</div>);
}
componentDidMount() {
this.disposer = autorun(() => {
console.log(this.count);
});
when(
() => this.count === 'wrong',
() => console.log(this.count)
);
}
}
上面这段代码很简单:
- 通过observable装饰器装饰一个属性,这个属性变动之后,就会自动触发响应的动作,这里的属性可以是string,boolean,array,object等
- autorun,when,observer都是reactions,当observable装饰的可观察属性发生变化时,会触发它们自动执行
- observer的作用是,当可观察属性发生变化时,调用react组件的render方法重新渲染视图
- autorun,当autorun函数中依赖的可观察属性发生变化时,就会自动触发autorun函数的执行,同时,autorun函数返回一个函数,调用该函数将会在执行期间清理 autorun。
- when,该函数接受两个函数作为参数,第一个函数返回一个判断条件;在该判断条件满足时,将会执行第二个函数;when函数只会执行一次。
- computed的行为跟vue中的computed行为一直,它与autorun的区别简单的说,computed需要被使用才会触发自动计算,被使用可以是在视图中渲染这个值,也可以是其他reactions
然后再看另一个例子:
import * as React from 'react';
import { observable, intercept, observe } from 'mobx';
import { observer } from 'mobx-react';
import { ChangeEvent } from 'react';
@observer
export default class Item extends React.Component {
@observable
apple = {
name: 'apple'
};
disposer1: () => void;
disposer2: () => void;
handleInput = (event: ChangeEvent<HTMLInputElement>) => {
this.apple.name = event.target.value;
}
render() {
return (
<div>
<h3>{this.apple.name}</h3>
<input type="text" onChange={this.handleInput}/>
</div>
);
}
componentDidMount() {
this.disposer1 = intercept(this.apple, 'name', (change) => {
if (change.newValue === 'hello') {
this.disposer1();
}
change.newValue = 'hi' + change.newValue;
return change;
});
this.disposer2 = observe(this.apple, 'name', (change) => {
console.log(change.newValue);
console.log(change.oldValue);
});
}
}
这段示例代码有如下需要注意的点:
- observe/intercept都能拦截变化值(注意这里是observe,不是observer)
- 他们都会返回一个函数,执行函数将会清理掉对应的observe或者intercept
- intercept会在observe之前执行,可以在intercept中对可观察的值进行修改,修改后的值会反映到视图中去,也可以返回null,那么对应值的修改将不会触发对应的reactions
- observe中不能修改可观察的值,对应的回调函数中能够拿到oldValue和newValue,使用方法跟vue中的watch类似
接下来是第三个例子:
// apple.ts
import { action, observable } from 'mobx';
class Apple {
@observable
name: string = '';
@action
async setName(value: string) {
const result = await new Promise((resolve) => {
setTimeout(() => {
resolve(value);
}, 4000);
});
this.name = result as string;
}
}
export default new Apple();
//Five.tsx
import * as React from 'react';
import { observer } from 'mobx-react';
import apple from './apple';
@observer
export default class Five extends React.Component {
apple = apple;
setName = () => {
this.apple.setName('hello world');
}
render() {
return (
<div>
{this.apple.name}
<button onClick={this.setName}>change</button>
</div>);
}
}
这其中有一下需要注意的点:
- 相较于redux中使用middleware来处理异步,mobx中不需要那么复杂,只需要使用async/await来优雅的处理异步的逻辑
- mobx中推荐使用单例模式来管理各种数据流,就像上面的Apple类,封装了可观察值的属性以及对应的操作逻辑,然后new一个实例并导出
- 在react中使用mobx,可以不需要操作state或props,直接赋值给类的属性即可
最后一个例子是这样:
import * as React from 'react';
import { inject, observer, Provider } from 'mobx-react';
import { observable } from 'mobx';
import { ChangeEvent } from 'react';
interface CommonProps {
}
interface ContextProps extends CommonProps {
color: string;
}
@inject('color')
class Message extends React.Component<CommonProps> {
get contextProps() {
return this.props as ContextProps;
}
render() {
return (
<div>{this.contextProps.color}</div>
);
}
}
const MessageWrap => <Message/>;
@observer
export default class Container extends React.Component {
@observable
color: string = 'red';
changeColor = (event: ChangeEvent<HTMLInputElement>) => {
this.color = String(event.target.value) as string;
}
render() {
return (
<Provider color={this.color}>
<div className="name">
<MessageWrap/>
<input onChange={this.changeColor}/>
</div>
</Provider>
);
}
}
这个例子其实使用了mobx中的 Provider 和 inject,其利用了react中的context API,能够实现跨组件传递数据的功能,其中的Message组件中有一个contextProps属性。
上面的内容简短的介绍了mobx的api和使用方式,mobx的api比较简洁,需要了解的不多。接下来将分析一下mobx的适用场景以及和redux的对比。
首先是 redux 和 mobx 的对比:
- redux 将数据保存在单一的 store 中,mobx 将数据保存在分散的多个 store 中
- redux 使用 plain object 保存数据,需要手动处理变化后的操作;mobx 适用 observable 保存数据,数据变化后自动处理响应的操作
- redux 使用不可变状态,这意味着状态是只读的,不能直接去修改它,而是应该返回一个新的状态,同时使用纯函数;mobx 中的状态是可变的,可以直接对其进行修改
- mobx 相对来说比较简单,在其中有很多的抽象,mobx更多的使用面向对象的编程思维;redux 会比较复杂,因为其中的函数式编程思想掌握起来不是那么容易,同时需要借助一系列的中间件来处理异步和副作用
- mobx 中有更多的抽象和封装,调试会比较困难,同时结果也难以预测;而 redux 提供能够进行时间回溯的开发工具,同时其纯函数以及更少的抽象,让调试变得更加的容易
基于以上的对比,通常会有结论说:对于一些简单的,规模不大的应用来说,用 mobx 就足够了。但是是不是一些大的,复杂的应用就不能用 mobx 呢?我觉得不一定。其实如果能够合理的组织代码的结构,理清依赖关系,一些复杂的场景适用 mobx 也是可以的,不过 mobx 目前对 react ssr 的支持还没有 redux 那么完善,这也是急需改善的点。
相较于 redux,mobx 中存在着一些限制:
- 它不会分析你的数据中是否存在着循环依赖
- 它不会预先假定你的数据结构是 plain object,class 或是任何其他的数据类型
因此,mobx 不能保证用户提供的数据一定能 JSON 序列化,或者能在有限的时间遍历完。所以它更应该被认为是一个数据流管理工具,能够让你以较小的代价构建自己的状态管理架构。能够快捷的再现有的项目中使用,而不需要进行大规模的重写。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论