1 https://zh.mobx.js.org/README.html
2 https://juejin.cn/post/7046710251382374413
3 https://cn.mobx.js.org/refguide/observable.html
mobx入门基础教程-慕课网
Mobx学习 - 掘金
十分钟入门 MobX & React
十分钟入门 MobX & React
概念
MobX区分了应用程序中的以下三个概念:
- State(状态)
- Actions(动作)
- Derivations(派生)
让我们仔细看看下面的这些概念,或者在10分钟的MobX和React简介中,您可以通过交互方式逐步深入了解这些概念,并构建一个简单的待办事项列表(Todo List)应用程序。
1 定义 State 并使其可观察
State(状态) 是驱动你的应用程序的数据。
通常来说,状态有领域特定状态(比如 Todo List 中的列表项数据)和视图状态 (比如当前选中的列表元素)。State 就像保存着数据的电子表格单元格一样。
将 State 存储在任何您喜欢的数据结构中:普通对象、数组、类、循环数据结构或引用。这与MobX的工作方式无关。
只要确保所有你想随时间改变的属性都被标记为observable
,这样MobX就可以跟踪它们。
以下是一个简单的示例:
import { makeObservable, observable, action } from "mobx"class Todo {
id = Math.random()
title = ""
finished = falseconstructor(title) {
makeObservable(this, {
title: observable,
finished: observable,
toggle: action
})
this.title = title
}
toggle() {
this.finished = !this.finished
}
}
提示: 在这个例子中我们可以用 makeAutoObservable
对其进行简化,但是为了能更详细的展示不同的概念,我们对其进行显式设置。
使用 observable
就像将对象的属性放在Excel表格的单元格中。但是和单元格不同的是,他们的值不仅仅是数值,也可以是引用、对象和数组。
接下来我们看一下被我们标记为 action
的toggle
2 使用 Action 更新 State
Action(动作) 是任意可以改变 State(状态) 的代码,比如用户事件处理、后端推送数据处理、调度器事件处理等等。
Action 就像用户在Excel单元格中输入了新的值。
在 Todo
类中,我们可以看到 toggle
方法改变了 finished
属性的值,而 finished
是被标记为 observable
的。建议您将所有修改 observable
值的代码标记为 action
。MobX 可以自动进行事务处理以轻松实现最佳性能。
使用 Action 可以帮助您更好地组织代码,并防止您在无意中修改 State。
在 MobX 术语中,可以修改 State 的方法被称为 action(动作) 。这与基于当前状态来生成新信息的 view(视图) 是不同的。 您代码中的每一个方法只应完成上述两个目标中的一个。
3 创建 Derivations 以便自动对 State 变化进行响应
任何 来源是State(状态) 并且不需要进一步交互的东西都是 Derivation(派生)。
Derivations 包括许多方式:
- 用户界面
- 派生数据 , 比如剩余未完成
todos
的数量 - 后端集成 , 比如发送改变到服务器端
Mobx 区分了两种 Derivation :
- Computed values,总是可以通过纯函数从当前的可观测 State 中派生。
- Reactions, 当 State 改变时需要自动运行的副作用 (命令式编程和响应式编程之间的桥梁)
当最开始使用MobX时,人们容易过度使用 Reaction。
黄金法则是,如果要基于当前 State 创建值,请始终使用 computed。
3.1. 通过 computed 对派生值进行建模
你可以通过定义 getter 方法并使用 makeObservable
将其标记为 computed
的方式创建一个 computed 值。
import { makeObservable, observable, computed } from "mobx"class TodoList {
todos = []
get unfinishedTodoCount() {
return this.todos.filter(todo => !todo.finished).length
}
constructor(todos) {
makeObservable(this, {
todos: observable,
unfinishedTodoCount: computed
})
this.todos = todos
}
}
Mobx 会确保 unfinishedTodoCount
会在todos数组发生变化中或者 todos中的一个对象中的 finished
属性被修改时自动更新。
这些计算类似于 Excel 单元格中的公式。它们仅在需要时自动更新。也就是说,如果有观察者使用其结果时才会更新。也就是说,如果有有人关心其结果时才会更新。
3.2. 使用 reaction 对副作用建模
作为用户,要想在屏幕上看到状态或计算值的变化,就需要一个重新绘制部分GUI的 reactions 。
Reaction 和 computed 类似,但并不产生信息,而是产生副作用,如打印到控制台、发出网络请求、增量更新 React 组件树以便更新DOM等。
简而言之,reaction 是 响应式编程和指令式编程之间的桥梁。
到目前为止,最常用的 reaction 形式是UI组件。 注意,action 和 reaction 都可能引起副作用。 副作用应有一个清晰的、显式的起源,例如在提交表单时发出网络请求,应该从相关的事件处理程序显式触发。
3.3. 响应式 React 组件
如果使用 React,你可以将组件用安装中下载的包中的observer
函数来包装起来,以便让组件成为响应式的。在这个示例中,我们将用更轻量的 mobx-react-lite
包。
import * as React from "react"import { render } from "react-dom"import { observer } from "mobx-react-lite"const TodoListView = observer(({ todoList }) => (
<div><ul>
{todoList.todos.map(todo => (
<TodoView todo={todo} key={todo.id} />
))}
</ul>
Tasks left: {todoList.unfinishedTodoCount}
</div>
))
const TodoView = observer(({ todo }) => (
<li><input type="checkbox" checked={todo.finished} onClick={() => todo.toggle()} />
{todo.title}
</li>
))
const store = new TodoList([new Todo("Get Coffee"), new Todo("Write simpler code")])
render(<TodoListView todoList={store} />, document.getElementById("root"))
observer
将 React 组件转化为了从数据到渲染的派生过程。 当使用 MobX 时,不存在“智能组件”和“木偶组件”。所有的组件在渲染时都是智能的,但是在定义时是按照木偶组件的方式去定义的。MobX会简单确定这个组件是否需要进行重绘,并止步于此。
因此,上述示例中的onClick
事件处理器调用toggle
Action 后,会使对应的TodoView
组件重绘,但仅当未完成任务的数量发生更改时才会使 TodoListView
组件重绘。
如果移除了Tasks left
这行代码(或者把他拆分到另一个组件中),TodoListView
组件就不再 toggle
执行时产生重绘了。
您可以查阅与 React 集成来了解更多有关 React 是如何与 MobX 协同运作的。
3.4. 自定义 Reaction
通常情况下你不需要使用它们,可以使用 autorun
,reaction
或 when
方法来订制你的特殊业务场景。
比如,下面的 autorun
将在unfinishedTodoCount
的数量发生变化时输出日志。
// 一个自动观察state的函数
autorun(() => {
console.log("Tasks left: " + todos.unfinishedTodoCount)
})
为什么每次 unfinishedTodoCount
发生改变时都会输出日志信息呢?答案是以下法则:
MobX对在执行跟踪函数期间读取的任何现有可观察属性作出响应。
要了解更多关于MobX如何确定需要对哪些可观察对象作出响应的信息,请查看 理解响应性 章节。
原则
Mobx 使用单向数据流,利用 action 改变 state ,进而更新所有受影响的 view
- 所有的 derivations 将在 state 改变时自动且原子化地更新。因此不可能观察中间值。
- 所有的 derivations 默认将会同步更新,这意味着 action 可以在 state 改变 之后安全的直接获得 computed 值。
- computed value 的更新是惰性的,任何 computed value 在需要他们的副作用发生之前都是不激活的。
- 所有的 computed value 都应是纯函数,他们不应该修改 state。
想了解更多背景,请查阅 MobX背后的基本原则
创建可观察状态
属性,完整的对象,数组,Maps 和 Sets 都可以被转化为可观察对象。 使得对象可观察的基本方法是使用 makeObservable
为每个属性指定一个注解。 最重要的注解如下:
-
observable
定义一个存储 state 的可追踪字段。 -
action
将一个方法标记为可以修改 state 的 action。 -
computed
标记一个可以由 state 派生出新的值并且缓存其输出的 getter。
像数组,Maps 和 Sets 这样的集合都将被自动转化为可观察对象。
makeObservable
用法:
-
makeObservable(target, annotations?, options?)
这个函数可以捕获已经存在的对象属性并且使得它们可观察。任何 JavaScript 对象(包括类的实例)都可以作为 target
被传递给这个函数。 一般情况下,makeObservable
是在类的构造函数中调用的,并且它的第一个参数是 this
。 annotations
参数将会为每一个成员映射 注解。需要注意的是,当使用 装饰器 时,annotations
参数将会被忽略。
派生数据并且接受参数的方法(例如:findUsersOlderThan(age: number): User[]
)不需要任何注解。 当我们从一个 reaction 中调用它们时,它们的读取操作仍然会被跟踪,但是为了避免内存泄漏,它们的输出将不会被记忆化。更详细的信息可以查看 MobX-utils computedFn {🚀}。
Mobx 通过 override
注解 支持子类的使用,但会有一些局限性。
import { makeObservable, observable, computed, action, flow } from "mobx"
class Doubler {
value
constructor(value) {
makeObservable(this, {
value: observable,
double: computed,
increment: action,
fetch: flow
})
this.value = value
}
get double() {
return this.value * 2
}
increment() {
this.value++
}
*fetch() {
const response = yield fetch("/api/value")
this.value = response.json()
}
}
所有带注解 的字段都是 不可配置的。
所有的不可观察(无状态)的字段(action, flow)都是 不可写的。
makeAutoObservable
使用:
-
makeAutoObservable(target, overrides?, options?)
makeAutoObservable
就像是加强版的 makeObservable
,在默认情况下它将推断所有的属性。你仍然可以使用 overrides
重写某些注解的默认行为。 具体来说,false
可用于从自动处理中排除一个属性或方法。 查看上面的代码分页获取示例。 与使用 makeObservable
相比,makeAutoObservable
函数更紧凑,也更容易维护,因为新成员不需要显式地提及。 然而,makeAutoObservable
不能被用于带有 super 的类或 子类。
推断规则:
- 所有 自有 属性都成为
observable
。 - 所有
get
ters 都成为 computed
。 - 所有
set
ters 都成为 action
。 - 所有 prototype 中的 functions 都成为
autoAction
。 - 所有 prototype 中的 generator functions 都成为
flow
。(需要注意,generators 函数在某些编译器配置中无法被检测到,如果 flow 没有正常运行,请务必明确地指定 flow
注解。) - 在
overrides
参数中标记为 false
的成员将不会被添加注解。例如,将其用于像标识符这样的只读字段。
import { makeAutoObservable } from "mobx"
function createDoubler(value) {
return makeAutoObservable({
value,
get double() {
return this.value * 2
},
increment() {
this.value++
}
})
}
注意,类也可以跟 makeAutoObservable 合用。 示例中的差异就展示了将 MobX 应用于不同编程风格的方法。
observable
用法:
-
observable(source, overrides?, options?)
observable
注解可以作为一个函数进行调用,从而一次性将整个对象变成可观察的。 source
对象将会被克隆并且所有的成员都将会成为可观察的,类似于 makeAutoObservable
做的那样。 同样,你可以传入一个 overrides
对象来为特定的成员提供特定的注解。 查看上面的代码获取示例。
由 observable
返回的对象将会使用 Proxy 包装,这意味着之后被添加到这个对象中的属性也将被侦测并使其转化为可观察对象(除非禁用 proxy)。
observable
方法也可以被像 arrays,Maps 和 Sets 这样的集合调用。这些集合也将被克隆并转化为可观察对象。
import { observable } from "mobx"
const todosById = observable({
"TODO-123": {
title: "find a decent task management system",
done: false
}
})
todosById["TODO-456"] = {
title: "close all tickets older than two weeks",
done: true
}
const tags = observable(["high prio", "medium prio", "low prio"])
tags.push("prio: for fun")
与第一个例子中的 makeObservable 不同,observable 支持为对象添加(和删除)字段。 这使得 observable 非常适合用于像动态键控的对象、数组、Maps 和 Sets 之类的集合。
例子: 可观察数组
下面的例子创建了一个可观察对象并且使用 autorun
观察它。 使用 Map 和 Set 集合时,用法和这里类似。
import { observable, autorun } from "mobx"const todos = observable([
{ title: "Spoil tea", completed: true },
{ title: "Make coffee", completed: false }
])
autorun(() => {
console.log(
"Remaining:",
todos
.filter(todo => !todo.completed)
.map(todo => todo.title)
.join(", ")
)
})
// 打印: 'Remaining: Make coffee'
todos[0].completed = false// 打印: 'Remaining: Spoil tea, Make coffee'
todos[2] = { title: "Take a nap", completed: false }
// 打印: 'Remaining: Spoil tea, Make coffee, Take a nap'
todos.shift()
// 打印: 'Remaining: Make coffee, Take a nap'
可观察的数组还有一些特别好用的实用函数:
-
clear()
从数组中清除所有元素。 -
replace(newItems)
将数组中现有的元素全部替换成 newItems。 -
remove(value)
根据 value 从数组中删除一个元素。如果找到并删除了元素,返回 true
。
可用的注解
注解 | 描述 |
observable observable.deep | 定义一个存储 state 的可跟踪字段。如果可能,任何被赋值给 observable 的字段都会基于它自己的类型被(深度)转化为observable、autoAction 或 flow。只有 plain object、array、Map、Set、function、generator function 可以转换,类实例和其他实例不会被影响。 |
observable.ref | 类似于 observable,但只有重新赋值才会被追踪。所赋的值会被完全忽略,并且将不会主动转化为 observable/autoAction/flow。比方说,在你打算将不可变数据存储在可观察字段中时,可以使用这个注解。 |
observable.shallow | 类似于 observable.ref 但是是用于集合的。任何所赋的集合都会被转化为可观察值,但是其内部的值并不会变为可观察值。 |
observable.struct | 类似于 observable,但是会忽略所赋的值中所有在结构上与当前值相同的值。 |
action | 把一个函数标记为会修改 state 的 action。查看 actions 获取更多信息。不可写。 |
action.bound | 类似于 action,但是会将 action 绑定到实例,因此将始终设置 this。不可写。 |
computed | 可以用在 getter 上,用来将其声明为可缓存的派生值。查看 computeds 获取更多信息。 |
computed.struct | 类似于 computed,但如果重新计算后的结果在结构上与之前的结果相等,那么观察者将不会收到通知。 |
TRUE | 推断最佳注解。查看 makeAutoObservable 获取更多信息。 |
FALSE | 刻意不为该属性指定注解。 |
flow | 创建一个 flow 管理异步进程。查看 flow 获取更多信息。需要注意的是,推断出来的 TypeScript 返回类型可能会出错。 不可写。 |
flow.bound | 类似于 flow, 但是会将 flow 绑定到实例,因此将始终设置 this。 不可写。 |
override | 用于子类覆盖继承的 action,flow,computed,action.bound。 |
autoAction | 不应被显式调用,但 makeAutoObservable 内部会对其进行调用,以便根据调用上下文将方法标识为 action 或者派生值。 |
使用 actions 更新 state
用法:
-
action
(注解) -
action(fn)
-
action(name, fn)
所有的应用程序都有 actions。action 就是任意一段修改 state 的代码。原则上,actions 总会为了对一个事件做出响应而发生。例如,点击了一个按钮,一些输入被改变了,一个 websocket 消息被送达了,等等。
尽管 makeAutoObservable
可以自动帮你声明一部分 actions,但是 MobX 还是要求你声明你的 actions。Actions 可以帮助你更好的组织你的代码并提供以下性能优势:
- 它们在 transactions 内部运行。任何可观察对象在最外层的 action 完成之前都不会被更新,这一点保证了在 action 完成之前,action 执行期间生成的中间值或不完整的值对应用程序的其余部分都是不可见的。
- 默认情况下,不允许在 actions 之外改变 state。这有助于在代码中清楚地对状态更新发生的位置进行定位。
action
注解应该仅用于会修改 state 的函数。派生其他信息(执行查询或者过滤数据)的函数不应该被标记为 actions,以便 MobX 可以对它们的调用进行跟踪。 带有 action
注解的成员是不可枚举的。
例子
makeObservable
import { makeObservable, observable, action } from "mobx"
class Doubler {
value = 0
constructor(value) {
makeObservable(this, {
value: observable,
increment: action
})
}
increment() {
// 观察者不会看到中间状态.
this.value++
this.value++
}
}
使用 action 包装函数
为了尽可能地利用 MobX 的事务性,actions 应该尽可能被传到外围。如果一个类方法会修改 state,可以将其标记为 action。把事件处理函数标记为 actions 就更好了,因为最外层的事务起着决定性作用。一个未被标记的、会接着调用两个 actions 的事件处理函数仍然将会生成两个事务。
为了帮助创建基于 action 的事件处理函数,action
不仅仅是一个注解,更是一个高阶函数。可以使用函数将它作为一个参数来调用,在这种情况下它将会返回一个有着相同签名的使用 action
包装过的函数。
例如在 React 中,可以按照下面的方式包装 onClick
事件处理函数。
const ResetButton = ({ formState }) => (
<buttononClick={action(e => {
formState.resetPendingUploads()
formState.resetValues()
e.stopPropagation()
})}
>
Reset form
</button>
)
为了更好的调试体验,我们推荐为被包装的函数命名,或者将名称作为 action
的第一个参数进行传递。
Actions 和继承
只有定义在原型上的函数可以被子类覆盖:
class Parent {
// on instance
arrowAction = () => {}
// on prototype
action() {}
boundAction() {}
constructor() {
makeObservable(this, {
arrowAction: action
action: action,
boundAction: action.bound,
})
}
}
class Child extends Parent {
// THROWS: TypeError: Cannot redefine property: arrowAction
arrowAction = () => {}
// OK
action() {}
boundAction() {}
constructor() {
super()
makeObservable(this, {
arrowAction: override,
action: override,
boundAction: override,
})
}
}
想要将单个的 action 绑定 到 this
,可以使用 action.bound
代替箭头函数。 查看 subclassing 获取更多信息。
异步 actions
从本质上讲,异步进程在 MobX 中不需要任何特殊处理,因为不论是何时引发的所有 reactions 都将会自动更新。 而且因为可观察对象是可变的,因此在 action 执行过程中保持对它们的引用一般是安全的。 然而,在异步进程中更新可观察对象的每个步骤(tick)都应该被标识为 action
。 我们可以通过利用上述的 API 以多种方式实现这一点,如下所示。
例如,在处理 Promise 时,更新 state 的处理程序应该被 action
包装起来,或者被标记为 actions,如下所示。
Actions 和继承
只有定义在原型上的函数可以被子类覆盖:
class Parent {
// on instance
arrowAction = () => {}
// on prototype
action() {}
boundAction() {}
constructor() {
makeObservable(this, {
arrowAction: action
action: action,
boundAction: action.bound,
})
}
}
class Child extends Parent {
// THROWS: TypeError: Cannot redefine property: arrowAction
arrowAction = () => {}
// OK
action() {}
boundAction() {}
constructor() {
super()
makeObservable(this, {
arrowAction: override,
action: override,
boundAction: override,
})
}
}
想要将单个的 action 绑定 到 this,可以使用 action.bound 代替箭头函数。
查看 subclassing 获取更多信息。
异步 actions
从本质上讲,异步进程在 MobX 中不需要任何特殊处理,因为不论是何时引发的所有 reactions 都将会自动更新。 而且因为可观察对象是可变的,因此在 action 执行过程中保持对它们的引用一般是安全的。 然而,在异步进程中更新可观察对象的每个步骤(tick)都应该被标识为 action。 我们可以通过利用上述的 API 以多种方式实现这一点,如下所示。
例如,在处理 Promise 时,更新 state 的处理程序应该被 action 包装起来,或者被标记为 actions,如下所示。
通过 computeds 派生信息
使用:
-
computed
(注解) -
computed(options)
(注解) -
computed(fn, options?)
计算值可以用来从其他可观察对象中派生信息。 计算值采用惰性求值,会缓存其输出,并且只有当其依赖的可观察对象被改变时才会重新计算。 它们在不被任何值观察时会被暂时停用。
从概念上讲,它们和电子表格中的公式非常相似,并且作用强大、不可被低估。它们可以协助减少你需要存储的状态的数量,并且是被高度优化过的。请尽可能使用它们。
例子
计算值可以通过在 JavaScript getters 上添加 computed
注解来创建。 使用 makeObservable
将 getter 声明为 computed。或者如果你希望所有的 getters 被自动声明为 computed
,可以使用 makeAutoObservable
,observable
或者 extendObservable
。
下面的示例依靠 Reactions {🚀} 高级部分中的 autorun
来辅助说明计算值的意义。
import { makeObservable, observable, computed, autorun } from "mobx"class OrderLine {
price = 0
amount = 1constructor(price) {
makeObservable(this, {
price: observable,
amount: observable,
total: computed
})
this.price = price
}
get total() {
console.log("Computing...")
return this.price * this.amount
}
}
const order = new OrderLine(0)
const stop = autorun(() => {
console.log("Total: " + order.total)
})
// Computing...// Total: 0console.log(order.total)
// (不会重新计算!)// 0
order.amount = 5// Computing...// (无需 autorun)
order.price = 2// Computing...// Total: 10
stop()
order.price = 3// 计算值和 autorun 都不会被重新计算.
上面的例子很好地展示了 计算值
的好处,它充当了缓存点的角色。 即使我们改变了 amount
,进而触发了 total
的重新计算, 也不会触发 autorun
,因为 total
将会检测到其输出未发生任何改变,所以也不需要更新 autorun
。
相比之下,如果 total
没有被注解,那么 autorun
会把副作用运行 3 次, 因为它将直接依赖于 total
和 amount
。自己试一下吧。
上图是为以上示例创建的依赖图。
规则
使用计算值时,请遵循下面的最佳实践:
- 它们不应该有副作用或者更新其他可观察对象。
- 避免创建和返回新的可观察对象。
- 它们不应该依赖非可观察对象的值
使用 reactions 处理副作用 {🚀}
reactions 是需要理解的重要概念,因为他可以将 MobX 中所有的特性有机地融合在一起。 reactions 的目的是对自动发生的副作用进行建模。 它们的意义在于为你的可观察状态创建消费者,以及每当关联的值发生变化时,自动运行副作用。
然而,理解了这一点之后,重要的是要认识到这里所讨论的 API 应该很少会被用到。 它们经常被抽象到其他的库里面(例如,mobx-react)或者你的应用程序中其他特定的抽象库。
但是,为了理解 MobX,让我们看一下如何创建 reactions。 最简单的方式是使用 autorun
工具函数。 除此之外,还有 reaction
和 when
。
Autorun
用法:
-
autorun(effect: (reaction) => void)
autorun
函数接受一个函数作为参数,每当该函数所观察的值发生变化时,它都应该运行。 当你自己创建 autorun
时,它也会运行一次。它仅仅对可观察状态的变化做出响应,比如那些你用 observable
或者 computed
注释的。
tracking 如何工作
Autorun 通过在响应式上下文运行 effect
来工作。在给定的函数执行期间,MobX 会持续跟踪被 effect 直接或间接读取过的所有可观察对象和计算值。 一旦函数执行完毕,MobX 将收集并订阅所有被读取过的可观察对象,并等待其中任意一个再次发生改变。 一旦有改变发生,autorun
将会再次触发,重复整个过程。
这就是下面的示例的工作方式。
例子
import { makeAutoObservable, autorun } from "mobx"class Animal {
name
energyLevel
constructor(name) {
this.name = name
this.energyLevel = 100
makeAutoObservable(this)
}
reduceEnergy() {
this.energyLevel -= 10
}
get isHungry() {
return this.energyLevel < 50
}
}
const giraffe = new Animal("Gary")
autorun(() => {
console.log("Energy level:", giraffe.energyLevel)
})
autorun(() => {
if (giraffe.isHungry) {
console.log("Now I'm hungry!")
} else {
console.log("I'm not hungry!")
}
})
console.log("Now let's change state!")
for (let i = 0; i < 10; i++) {
giraffe.reduceEnergy()
}
运行上面的代码,你将会看到下面的输出:
正如你在上面输出的前两行看到的,两个 autorun
函数在初始化时都会运行一次。这就是在运行 for
循环前可以看到的内容。
一旦我们运行 for
循环使用 reduceEnergy
action 改变 energyLevel
, 每当 autorun
观察到可观察状态的变化时, 我们将会看到一条新的 log 条目被打印出来:
- 对于“Energy level”函数,它总是可以检测到
energyLevel
可观察对象的变化,总共发生 10 次。 - 对于“Now I'm hungry”函数,它总是可以检测到
isHungry
计算值的变化, 总共发生 1 次。
Reaction
用法:
-
reaction(() => value, (value, previousValue, reaction) => { sideEffect }, options?)
.
reaction
类似于 autorun
,但可以让你更加精细地控制要跟踪的可观察对象。 它接受两个函数作为参数:第一个,data 函数,其是被跟踪的函数并且其返回值将会作为第二个函数,effect 函数,的输入。 重要的是要注意,副作用只会对 data 函数中被访问过的数据做出反应,这些数据可能少于 effect 函数中实际使用的数据。
一般的模式是在 data 函数中返回你在副作用中需要的所有数据, 并以这种方式更精确地控制副作用触发的时机。 与 autorun
不同,副作用在初始化时不会自动运行,而只会在 data 表达式首次返回新值之后运行。
例子: 数据和副作用函数
When
使用:
-
when(predicate: () => boolean, effect?: () => void, options?)
-
when(predicate: () => boolean, options?): Promise
when
会观察并运行给定的 predicate 函数,直到其返回 true
。 一旦 predicate 返回了 true,给定的 effect 函数就会执行并且自动执行器函数将会被清理掉。
如果你没有传入 effect
函数,when
函数返回一个 Promise
类型的 disposer,并允许你手动取消。
例子:以一种响应式的方式将值清理掉
await when(...)
如果你没有提供 effect
函数,when
将会返回一个 Promise
。这样会跟 async / await
很好地结合在一起,让你可以等待可观察对象中的变化。
async function() {
await when(() => that.isVisible)
// etc...
}
如果要提前取消 when
,可以对它返回的 Promise 调用 .cancel()
函数。
规则
这里是一些 reactive context 需要遵守的规则:
- 默认情况下,如果可观察对象发生了改变,受其影响的 reactions 会立即(同步)运行。然而,它们直到当前最外层的 (trans)action 执行结束后才会运行。
- autorun 只会跟踪给定函数在同步执行过程中所读取的可观察对象,不会跟踪异步发生的变化。
- autorun 不会跟踪被其调用的 action 所读取的可观察对象,因为 action 始终不会被追踪。
有关 MobX 会与不会对各种值作出响应的更多示例,请查看 理解响应性 部分。 对于依赖跟踪如何工作的更详细的技术细节,请阅读博客 Becoming fully reactive: an in-depth explanation of MobX。
Always dispose of reactions
传递给 autorun
,reaction
和 when
的函数只有在它们观察的所有对象都被 GC 之后才会被 GC。原则上,它们一直等待可观察对象发生新的变化。 为了阻止 reactions 永远地等待下去,它们总是会返回一个 disposer 函数,该函数可以用来停止执行并且取消订阅所使用的任何可观察对象。
const counter = observable({ count: 0 })
// 初始化一个 autorun 并且打印 0.const disposer = autorun(() => {
console.log(counter.count)
})
// 打印: 1
counter.count++
// 停止 autorun.
disposer()
// 不会打印消息.
counter.count++
我们强烈建议你,一旦不再需要这些方法中的副作用时,请务必调用它们所返回的 disposer 函数。 否则可能导致内存泄漏。
reaction
和 autorun
中 effect 函数的第二个参数 reaction
也可以被用来提前把 reaction 清理掉(通过调用 reaction.dispose()
)。
例子: 内存泄漏
谨慎地使用 reactions!
就像上面已经说过的那样,你不会经常创建 reactions。 很有可能你的应用程序不会直接使用这些 API 中的任何一个,而只会通过比如使用 mobx-react 绑定中的 observer
这样间接的方式创建出 reaction。
在你创建 reaction 之前,最好检查一下它是否符合下面几条原则:
- 只有在引起副作用的一方与副作用之间没有直接关系的情况下才使用 reaction: 如果一个副作用会为了响应很小的一组 events 或 actions 而执行,那么直接从那些特定的 action 中触发这个副作用通常会更容易理解。例如,如果按下表单提交按钮会导致一个 POST 网络请求的发送,那么为了响应
onclick
事件,直接触发这个副作用就会比通过 reaction 间接触发更容易理解。相比之下,如果你对表单状态的一切修改最后都会被自动存储到 localStorage,那么使用一个 reaction 可能就会很有帮助,这样你就不用在每个独立的 onChange
事件中触发这个副作用了。 - reactions 不应该更新其他可观察对象:这个 reaction 是否会修改其他可观察对象?如果答案是肯定的,那么你一般应该把你想要更新的可观察对象注解为
computed
值。例如,如果一个待办事项的集合 todos
发生了变化,那么请不要使用 reaction 来计算剩余待办 remainingTodos
的数量,而要把 remainingTodos
注解为计算值。这将使得代码更容易理解和调试。reaction 不应该计算生成新的数据,而只应该触发副作用。 - reactions 应该是独立的:你的代码是否依赖其他必须首先运行的 reaction?如果发生这种情况,你可能违反了第一条规则, 你可以选择将你需要创建的新 reaction 合并到它所依赖 reaction 中。MobX 并不能保证 reaction 的执行顺序。
有些实践并不符合上述原则。这就是为什么它们是原则,而不是法则。 但是,例外情况很少见,只有在万不得已的情况下才违反它们。
git clone -b dev-from-tag-3.3.0 http://10.8.59.16/Code/kubesphere/console.git
http://10.8.59.16/Code/kubesphere/console.git