MobX
MobX通过透明的函数响应式编程,使得其成为一种简单、可扩展的状态管理工具。使用MobX可以将应用编程响应式的编程方式。
其背后的原理是:任何源自应用状态的东⻄都应该自动地获得。
React和MobX是一对强力组合。React通过提高机制把应用状态转换为可渲染组件树并对其进行渲染;MobX提供机制来存储和更新应用状态供React使用。
1. 概念
MobX主要区分了概念:
- State(状态)
- Derivations(衍生)
- Actions(动作)。
在Derivations中又可以再细分为Computed values(计算值)与Reactions(反应)。
-
State(状态)
状态是驱动应用的数据。这其中包含对象,数组等。
-
Derivations(衍生)
任何源自状态并且不会再有任何进一步的相互作用的东西就是衍生
衍生以多种形式存在:
- 用户界面
- 衍生数据,比如剩下的待办事项的数量。
- 后端集成,比如把变化发送到服务器端。
MobX还区分了两种类型的衍生:
- Computed values(计算值):它们是永远可以使用纯函数(pure function)从当前可观察状态中衍生出的值。
- Reactions(反应):Reactions 是当状态改变时需要自动发生的副作用。需要有一个桥梁来连接命令式编程(imperative programming)和响应式编程(reactive programming)。或者说得更明确一些,它们最终都需要实现I / O 操作。
黄金法则: 如果你想创建一个基于当前状态的值时,请使用
computed
。 -
Actions(动作)
动作是任一一段可以改变状态的代码。例如用户事件、后端数据推送、预定事件、等等。
在MobX中可以显式地定义动作,它可以帮你把代码组织的更清晰。如果是在严格模式下使用MobX的话,MobX会强制只有在动作之中才可以修改状态。
2. 原则
MobX支持单向数据流,也就是动作改变状态,而状态的改变会更新所有受影响的视图。
- 当State(状态)改变时,所有Derivations(衍生)都会进行原子级的自动更新。因此永远不可能观察到中间值。
- 所有Derivations(衍生)默认都是同步更新。这意味着例如Actions(动作)可以在改变State(状态)之后直接可以安全地检查计算值。
- Computed values(计算值)是延迟更新的。任何不在使用State(状态)的 Computed values(计算值)将不会更新,直到需要它进行副作用(I / O)操作时。 如果视图不再使用,那么它会自动被垃圾回收。
- 所有的 Computed values(计算值)都应该是纯净的。它们不应该用来改变State(状态)。
3. API
装饰器
装饰器 | 说明 |
---|---|
@observable | 可以在 ES7 或者 TypeScript 类属性中属性使用,将其转换成可观察的。 @observable 可以在实例字段和属性 getter 上使用。 |
@action | 用于装饰可观察变量的状态的更新。在严格模式下,对于可观察变量的状态的更新必须使用此装饰器 |
@observer | 用来将 React 组件转变成响应式组件。它用 mobx.autorun 包装了组件的 render 函数以确保任何组件渲染中使用的数据变化时都可以强制刷新组件。 |
@computed | 如果已经启用 decorators 的话,可以在任意类属性的 getter 上使用 @computed 装饰器来声明式的创建计算属性。 |
其他
-
Autorun
当你想创建一个响应式函数,而该函数本身永远不会有观察者时,可以使用
mobx.autorun
。这通常是当你需要从反应式代码桥接到命令式代码的情况,例如打印日志、持久化或者更新UI的代码。
当使用
autorun
时,所提供的函数总是立即被触发一次,然后每次它的依赖关系改变时会再次被触发。 -
Reaction
它是
autorun
的变种,对于如何追踪observable
赋予了更细粒度的控制。它接收两个函数参数,第一个(数据 函数)是用来追踪并返回数据作为第二个函数(效果 函数)的输入。
不同于 autorun 的是当创建时效果 函数不会直接运行,只有在数据表达式首次返回一个新值后才会运行。 在执行 效果 函数时访问的任何 observable 都不会被追踪。
reaction
返回一个清理函数。用法:
reaction(() => data, (data, reaction) => { sideEffect }, options?)
-
enforceActions(严格模式)
在严格模式下,不允许在 action 外更改任何状态。 可接收的值:
- "never" (默认): 可以在任意地方修改状态
- "observed": 在某处观察到的所有状态都需要通过动作进行更改。在正式应用中推荐此严格模式。
- "always": 状态始终需要通过动作来更新(实际上还包括创建)。
-
runInAction: 异步 Actions
在严格模式下,所有修改可观察变量的地方必须(放在action中或)添加
@action
。但@action
只会对当前运行的函数作出反应,而不会对当前运行函数所调用的函数(不包含在当前函数之内)作出反应。这意味着如果@action
中存在setTimeout
、promise
的then
或async
语句,并且在回调函数中某些状态改变了,那么这些回调函数也应该包装在@action
中。这时候我们可以使用runInAction
包装修改可观察变量。mobx.configure({ enforceActions: "always" }); 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 / await
只是围绕基于promise
过程的语法糖。 结果是@action
仅应用于代码块,直到第一个await
。 在每个await
之后,一个新的异步函数将启动,所以在每个await
之后,状态修改代码应该被包装成动作。 这正是runInAction
再次派上用场的地方。mobx.configure({ enforceActions: "always" }); class Store { @observable githubProjects = [] @observable state = "pending" // "pending" / "done" / "error" @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" }) } } }
对比及总结
autorun
需要手动清理,@computed
会被自动清理。autorun
总是立即被触发一次,@computed
只有当它有自己的观察者时才会重新计算。autorun
用于执行打印日志、持久化或者更新UI的代码,@computed
用于产生一个新值。- 如果你有一个函数应该自动运行,但不会产生一个新的值,请使用
autorun
。 其余情况都应该使用@computed
。
4. 使用
安装MobX
yarn add mobx --save
yarn add mobx-react --save
MobX推荐使用ES7的decorator语法,以实现最精炼的表达。安装装饰器支持:
npm i --save-dev babel-plugin-transform-decorators-legacy
然后在 .babelrc 文件中启用它:
{
"presets": ["module:metro-react-native-babel-preset"],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
[
"@babel/transform-runtime",
{
"helpers": true,
"regenerator": false
}
]
]
}
一些问题
最新的5.x以上的mobx在安装时存在问题,这里我自己使用的是4.x
另外对于Babel也需要使用
贴出自己的package.json
"dependencies": {
"babel-core": "^6.26.3",
"babel-preset-stage-0": "^6.24.1",
"mobx": "^4.3.1",
"mobx-react": "^5.1.0",
"react": "16.6.1",
"react-native": "0.57.7"
},
"devDependencies": {
"@babel/core": "^7.2.0",
"@babel/plugin-proposal-decorators": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.2.0",
"@babel/runtime": "^7.2.0",
"babel-jest": "23.6.0",
"babel-plugin-transform-decorators-legacy": "^1.3.5",
"babel-preset-react-native-stage-0": "^1.0.1",
"jest": "23.6.0",
"metro-react-native-babel-preset": "0.50.0",
"react-test-renderer": "16.6.1"
}