使用 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
部分。例如,这在单元测试设置中非常有用,因为警告并不总是具有太多价值。