参考链接:
Mobx官方文档
前言
Mobx是一个透明函数响应式编程(Transparently Functional Reactive Programming,TFRP)的状态管理库,它使得状态管理简单可伸缩:
1. 基本概念
- actions:一些改变状态值(state)的动作。
- state:可观察的状态值
- computed value:根据state,用pure function计算出来的值
- reactions:因state或computed value变化而引起的反应,主要指视图UI重新渲染
2. 基本使用
安装:
npm install mobx --save
npm install mobx-react --save
复制代码
创建一个mobx store的实例:
import {observable, computed, action} from "mobx";
class Store{
@observable cities = [];
@computed
get total() {
return this.cities.length;
}
@action.bound
clearCities() {
this.cities = [];
}
@action.bound
add(values) {
this.cities = values;
}
}
export new Store()
复制代码
@obserbale
表示定义一个state值@computed
计算属性,表示根据现有的数据计算出来的衍生值,将函数当作属性(函数的return值)用@action.bound
或@aciton
:表示定义更新state的方法,里面是更新state的逻辑,通过这种方式可以直接通过this.xxx来修改state
2.1 含异步操作的action
mobx不允许在@action中的Promise.then或async @aciton等异步操作中直接进行state的数据更新操作
官方解释:
action 包装/装饰器只会对当前运行的函数作出反应,而不会对当前运行函数所调用的函数(不包含在当前函数之内)作出反应! 这意味着如果 action 中存在 setTimeout、promise 的 then 或 async 语句,并且在回调函数中某些状态改变了,那么这些回调函数也应该包装在 action 中。
一种可行的做法是针对Promise的then回调中修改state的部分也封装成一个@aciton,则调用该aciton而不是在回调中直接修改则可以解决该问题
推荐的做法是使用mobx提供的runInAction
来修改state:
Promise.then中:
class Store {
@observable githubProjects = []
@observable state = "pending" // "pending" / "done" / "error"
@action
fetchProjects() {
this.githubProjects = []
this.state = "pending"
fetchGithubProjectsSomehow().then(
projects => {
const filteredProjects = somePreprocessing(projects)
// 将‘“最终的”修改放入一个异步动作中
runInAction(() => {
this.githubProjects = filteredProjects
this.state = "done"
})
},
error => {
// 过程的另一个结局:...
runInAction(() => {
this.state = "error"
})
}
)
}
}
复制代码
async的action中:
@action
async fetchProjects() {
this.githubProjects = []
this.state = "pending"
try {
const projects = await fetchGithubProjectsSomehow()
const filteredProjects = somePreprocessing(projects)
// await 之后,再次修改状态需要动作:
runInAction(() => {
this.state = "done"
this.githubProjects = filteredProjects
})
} catch (error) {
runInAction(() => {
this.state = "error"
})
}
}
复制代码
2.2 @action.bound和@aciton的区别
官方解释:
action 装饰器/函数遵循 javascript 中标准的绑定规则。 但是,action.bound 可以用来自动地将动作绑定到目标对象(即当前Store的实例)。
注意: action.bound 不要和箭头函数一起使用;箭头函数已经是绑定过的并且不能重新绑定。
示例如下:
2.3 页面使用mobx
在需要的页面中,引入对应Store的实例,并使用mobx-react的@observer进行数据监听,以实现数据的展示以及数据更新来驱动试图的更新
Store.js
import {observable, computed, action} from "mobx";
class Store{
@observable cities = ["北京","上海"];
@observable name = "scw";
@computed
get total() {
return this.cities.length;
}
@action.bound
clearCities() {
this.cities = [];
}
@action.bound
add(values) {
this.cities = values;
}
}
export new Store()
复制代码
页面组件:
import { observer, inject } from "mobx-react"
import Store from "./Store"
@observer
class Page extends Component {
componentDidMount() {
Store.add("南京")
}
render() {
return (
<div>
{Store.name}
{Store.ciies.map(item=><span>{item}</span>)}
</div>
)
}
}
export default Page
复制代码
2.3 mobx的全局注入
mobx不会强制像redux那样必须全局注入store,如2.2所示,在mobx中,我们可以针对每个特定的页面或者功能编写专有的store来使用。
不过它也可以在全局注入单个或多个Store来供全局页面组件使用。一般用于一些公共状态或公共方法的设置
所有store的统一管理文件:
你可以在这里引入一些公共的Store,统一封装成对象
import City from "./city";
export default {
city: new City()
};
复制代码
顶层组件:
这里引入mobx-react的Providr进行公共Store的全局注入:
import React from "react";
import {render} from "react-dom";
import {Provider} from "mobx-react";
import {Router, browserHistory, hashHistory} from "react-router";
import { RouterStore, syncHistoryWithStore } from "mobx-react-router";
import routes from "./routes";
import store from "./mobstore";
const routingStore = new RouterStore();
const history = syncHistoryWithStore(hashHistory, routingStore);
store.routing = routingStore;
render(
<Provider {...store}>
<Router history={history} routes={routes}/>
</Provider>,
document.querySelector(".container")
);
复制代码
组件使用全局注入的Store:
import React, {Component} from "react";
import {observer, inject} from "mobx-react";
@inject("city")
@observer
export default class CitySelector extends Component {
constructor(...props) {
super(...props);
this.state = {
city: []
};
}
onChange = (value) => {
this.props.city.add(value);
};
clearCities() {
this.props.city.clearCities();
}
render() {
let { cities, total } = this.props.city;
....
复制代码
使用@inject("xxx")
来调用公共Store对象中指定属性名的Store,此时该组件可通过this.props.xxx
来访问对应的Store。包括里面的数据和方法即@observable和@aciton
3. Redux vs Mobx
(1)单一store和多store
store是应用管理数据的地方,在Redux应用中,我们总是将所有共享的应用数据集中在一个大的store中,
而Mobx则通常按模块将应用状态划分,在多个独立的store中管理。
(2)JavaScript对象和可观察对象
Redux默认以JavaScript原生对象形式存储数据,而Mobx使用可观察对象:
- Redux需要手动追踪所有状态对象的变更;
- Mobx中可以监听可观察对象,当其变更时将自动触发监听;
(3)不可变(Immutable)和可变(Mutable)
Redux状态对象通常是不可变的(Immutable),我们不能直接操作状态对象,而总是在原来状态对象基础上返回一个新的状态对象,这样就能很方便的返回应用上一状态;
而Mobx中可以直接使用新值更新状态对象。
(4)mobx-react和react-redux
使用Redux和React应用连接时,需要使用react-redux提供的Provider和connect:
- Provider:负责将Store注入React应用;
- connect:负责将store state注入容器组件,并选择特定状态作为容器组件props传递;
对于Mobx而言,同样需要两个步骤:
- Provider:使用mobx-react提供的Provider将所有stores注入应用;
- 使用inject将特定store注入某组件,store可以传递状态或action;然后使用observer保证组件能响应store中的可观察对象(observable)变更,即store更新,组件视图响应式更新。
选择Mobx的原因
学习成本少:Mobx基础知识和配置很简单,而Redux确较繁琐,流程较多,需要配置,创建store,编写reducer,action,如果涉及异步任务,还需要引入redux-thunk或redux-saga编写额外代码,Mobx流程相比就简单很多,并且不需要额外异步处理库;
不选择Mobx的原因
过于自由:Mobx提供的约定及模版代码很少,这导致开发代码编写很自由,如果不做一些约定,比较容易导致团队代码风格不统一,所以当团队成员较多时,确实需要添加一些约定;也正因为这样,Mobx可拓展,可维护性难度较高