使用 action 更新状态
用法
action(注解)action(fn)action(name, fn)@action(方法/字段装饰器)
所有应用程序都有 action。action 是任何修改状态的代码段。原则上,action 总是响应事件而发生。例如,点击按钮、更改输入、收到 websocket 消息等。
MobX 要求您声明 action,尽管 makeAutoObservable 可以自动化许多工作。action 帮助您更好地构建代码,并提供以下性能优势:
它们在 事务 内运行。在最外层的 action 完成之前,不会运行任何反应,保证在 action 期间产生的中间值或不完整值在 action 完成之前不会对应用程序的其余部分可见。
默认情况下,不允许在 action 之外更改状态。这有助于在您的代码库中清晰地识别状态更新发生的位置。
action 注解仅应用于打算修改状态的函数。推导出信息(执行查找或过滤数据)的函数不应标记为 action,以允许 MobX 跟踪它们的调用。action 注释的成员将不可枚举。
示例
import { makeObservable, observable, action } from "mobx"
class Doubler {
value = 0
constructor() {
makeObservable(this, {
value: observable,
increment: action
})
}
increment() {
// Intermediate states will not become visible to observers.
this.value++
this.value++
}
}
import { observable, action } from "mobx"
class Doubler {
@observable accessor value = 0
@action
increment() {
// Intermediate states will not become visible to observers.
this.value++
this.value++
}
}
import { makeAutoObservable } from "mobx"
class Doubler {
value = 0
constructor() {
makeAutoObservable(this)
}
increment() {
this.value++
this.value++
}
}
import { makeObservable, observable, action } from "mobx"
class Doubler {
value = 0
constructor() {
makeObservable(this, {
value: observable,
increment: action.bound
})
}
increment() {
this.value++
this.value++
}
}
const doubler = new Doubler()
// Calling increment this way is safe as it is already bound.
setInterval(doubler.increment, 1000)
import { observable, action } from "mobx"
const state = observable({ value: 0 })
const increment = action(state => {
state.value++
state.value++
})
increment(state)
import { observable, runInAction } from "mobx"
const state = observable({ value: 0 })
runInAction(() => {
state.value++
state.value++
})
使用 action 包装函数
为了尽可能地利用 MobX 的事务特性,action 应该尽可能地向外传递。如果一个类方法修改了状态,最好将其标记为 action。标记事件处理程序为 action 甚至更好,因为最外层的事务才是最重要的。单个未标记的事件处理程序,如果随后调用两个 action,仍然会生成两个事务。
为了帮助创建基于 action 的事件处理程序,action 不仅是一个注解,也是一个高阶函数。它可以接受一个函数作为参数,在这种情况下,它将返回一个具有相同签名的 action 包装函数。
例如,在 React 中,一个 onClick 处理程序可以像下面这样被包装。
const ResetButton = ({ formState }) => (
<button
onClick={action(e => {
formState.resetPendingUploads()
formState.resetValues()
e.preventDefault()
})}
>
Reset form
</button>
)
为了调试的目的,我们建议您命名包装的函数,或者将名称作为第一个参数传递给 action。
注意: action 是未跟踪的
action 的另一个特性是它们是 未跟踪的。当从副作用或计算属性(非常罕见!)内部调用一个 action 时,action 读取的可观察对象不会计入派生依赖项。
makeAutoObservable、extendObservable 和 observable 使用一种特殊的 action,称为 autoAction,它将在运行时确定函数是派生函数还是 action。
action.bound
用法
action.bound(注解)
action.bound 注解可用于自动将方法绑定到正确的实例,以便 this 始终在函数内部被正确绑定。
提示: 使用 makeAutoObservable(o, {}, { autoBind: true }) 自动绑定所有 action 和流程
import { makeAutoObservable } from "mobx"
class Doubler {
value = 0
constructor() {
makeAutoObservable(this, {}, { autoBind: true })
}
increment() {
this.value++
this.value++
}
*flow() {
const response = yield fetch("http://example.com/value")
this.value = yield response.json()
}
}
runInAction
用法
runInAction(fn)
使用此实用程序来创建一个立即调用的临时 action。在异步进程中可能很有用。查看上面的代码块以获取示例。
Action 和继承
仅定义在原型上的 action 可以被子类覆盖
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 代替箭头函数。
有关更多信息,请参阅子类化。
异步 action
从本质上讲,异步进程不需要在 MobX 中进行任何特殊处理,因为所有反应都会自动更新,无论它们是在什么时间点触发的。由于可观察对象是可变的,因此通常可以安全地保留对它们的引用,以供 action 的持续时间使用。但是,异步进程中更新可观察对象的每个步骤(滴答)都应标记为 action。这可以通过多种方式实现,利用上面的 API,如下所示。
例如,在处理 promise 时,更新状态的处理程序应该是 action,或者应该使用 action 包装,如下所示。
promise 解析处理程序是在行内处理的,但在原始 action 完成后运行,因此需要用 action 包装它们
import { action, makeAutoObservable } from "mobx"
class Store {
githubProjects = []
state = "pending" // "pending", "done" or "error"
constructor() {
makeAutoObservable(this)
}
fetchProjects() {
this.githubProjects = []
this.state = "pending"
fetchGithubProjectsSomehow().then(
action("fetchSuccess", projects => {
const filteredProjects = somePreprocessing(projects)
this.githubProjects = filteredProjects
this.state = "done"
}),
action("fetchError", error => {
this.state = "error"
})
)
}
}
如果 promise 处理程序是类字段,它们将被 makeAutoObservable 自动包装在 action 中
import { makeAutoObservable } from "mobx"
class Store {
githubProjects = []
state = "pending" // "pending", "done" or "error"
constructor() {
makeAutoObservable(this)
}
fetchProjects() {
this.githubProjects = []
this.state = "pending"
fetchGithubProjectsSomehow().then(this.projectsFetchSuccess, this.projectsFetchFailure)
}
projectsFetchSuccess = projects => {
const filteredProjects = somePreprocessing(projects)
this.githubProjects = filteredProjects
this.state = "done"
}
projectsFetchFailure = error => {
this.state = "error"
}
}
await 之后的任何步骤都不在同一个滴答中,因此它们需要 action 包装。在这里,我们可以利用 runInAction
import { runInAction, makeAutoObservable } from "mobx"
class Store {
githubProjects = []
state = "pending" // "pending", "done" or "error"
constructor() {
makeAutoObservable(this)
}
async fetchProjects() {
this.githubProjects = []
this.state = "pending"
try {
const projects = await fetchGithubProjectsSomehow()
const filteredProjects = somePreprocessing(projects)
runInAction(() => {
this.githubProjects = filteredProjects
this.state = "done"
})
} catch (e) {
runInAction(() => {
this.state = "error"
})
}
}
}
import { flow, makeAutoObservable, flowResult } from "mobx"
class Store {
githubProjects = []
state = "pending"
constructor() {
makeAutoObservable(this, {
fetchProjects: flow
})
}
// Note the star, this a generator function!
*fetchProjects() {
this.githubProjects = []
this.state = "pending"
try {
// Yield instead of await.
const projects = yield fetchGithubProjectsSomehow()
const filteredProjects = somePreprocessing(projects)
this.state = "done"
this.githubProjects = filteredProjects
return projects
} catch (error) {
this.state = "error"
}
}
}
const store = new Store()
const projects = await flowResult(store.fetchProjects())
使用 flow 代替 async / await {🚀}
用法
flow(注解)flow(function* (args) { })@flow(方法装饰器)
flow 包装器是 async / await 的可选替代方案,它使使用 MobX action 变得更容易。flow 接受一个 生成器函数 作为其唯一的输入。在生成器内部,您可以通过 yield promise 来链接 promise(而不是 await somePromise,您写 yield somePromise)。然后,flow 机制将确保生成器在 yield promise 解析时继续或抛出。
所以 flow 是 async / await 的替代方案,它不需要任何进一步的 action 包装。它可以按以下方式应用
- 将
flow包装在您的异步函数周围。 - 不要使用
async,而是使用function *。 - 不要使用
await,而是使用yield。
上面的 flow + 生成器函数 示例显示了这在实践中是如何实现的。
请注意,flowResult 函数仅在使用 TypeScript 时才需要。由于用 flow 装饰了一个方法,它将把返回的生成器包装在一个 promise 中。但是,TypeScript 并不知道这种转换,因此 flowResult 将确保 TypeScript 了解这种类型更改。
makeAutoObservable 及其朋友将自动推断生成器为 flow。flow 注释的成员将不可枚举。
{🚀} 注意: 在对象字段上使用 flow
flow 与 action 一样,可以用来直接包装函数。上面的示例也可以这样写
import { flow, makeObservable, observable } from "mobx"
class Store {
githubProjects = []
state = "pending"
constructor() {
makeObservable(this, {
githubProjects: observable,
state: observable,
})
}
fetchProjects = flow(function* (this: Store) {
this.githubProjects = []
this.state = "pending"
try {
// yield instead of await.
const projects = yield fetchGithubProjectsSomehow()
const filteredProjects = somePreprocessing(projects)
this.state = "done"
this.githubProjects = filteredProjects
} catch (error) {
this.state = "error"
}
})
}
const store = new Store()
const projects = await store.fetchProjects()
好处是,我们不再需要 flowResult,坏处是 this 需要进行类型化,以确保其类型被正确推断。
flow.bound
用法
flow.bound(注解)
flow.bound 注解可以用于自动将方法绑定到正确的实例,这样 this 始终在函数内部正确绑定。类似于操作,流可以通过 autoBind 选项 默认绑定。
取消流 {🚀}
流的另一个好处是它们可以取消。flow 的返回值是一个 Promise,它在生成器函数最终返回的值解析。返回的 Promise 有一个额外的 cancel() 方法,它将中断正在运行的生成器并取消它。任何 try / finally 子句仍将运行。
禁用强制操作 {🚀}
默认情况下,MobX 6 及更高版本要求您使用操作来更改状态。但是,您可以配置 MobX 来禁用此行为。查看 enforceActions 部分。例如,这在单元测试设置中非常有用,因为警告并不总是具有太多价值。
