使用actions
更新状态
用法:
-
action
注解(es
规范中称之为装饰器,感觉Mobx
的文档描述不是很准确,但是后面翻译已原文为准) action(fn)
action(name, fn)
所有的应用程序都有action
。一个action
是用于修改state
的任意一段代码片段。原则上,action
总是响应event
而触发。例如:点击了按钮、输入一些内容、接收到新的websocket
推送消息等等。
MobX
要求你声明动作,尽管makeAutoObservable
可以自动完成很多工作。
Actions
能够帮助你更好的组织代码并且提供以下的性能优势:
-
action
在transactions
中执行。在最外层的action
完成之前,不会更新任何观察者(比如computed
的值,或者使用了observer
的react
组件),以确保在动作完成之前,动作的过程中产生的中间值或不完整值对应用程序的其余部分不可见。 - 默认情况下,不允许在
action
之外的地方更新state
(这一点和之前的版本有区别,之前的版本需要手动开启严格模式, 具体可参考changelog)。这有助于清楚的定位触发state
更新在代码库中的位置。 -
action
注解应该只在修改state
的函数上使用。那些用来派生出其他的信息函数(比如 查询特定的值,过滤等等)不应该使用action
注解,这样Mobx
能够跟踪这些函数的调用(其实这里的情况应该使用computed
)
makeObserable & makeAutoObservable & action.bound & arrow function
使用action
包裹函数
为了尽可能利用Mobx
的事务特性,action
应该尽可能的向外传递(最开始意图触发修改state
的函数)。如果一个类的方法修改的state
,最好将其标记为action
。最好将事件处理函数标记为action
,因为它是最外层的事务。
随后调用两个动作的一个未被标记action
的事件处理函数将会生成两个事务。
为了方便创建基于事件回调的动作,action
不仅仅是一个注解,也是一个高阶函数。它可以传入一个函数作为参数来调用。在那种场景下,它将返回一个具有同样签名的包装action
。
例如在React
里,一个onClick
处理器可以如下包装:
const ResetButton = ({ formState }) => (
<button
onClick={action(e => {
formState.resetPendingUploads()
formState.resetValues()
e.stopPropagation()
})}
>
Reset form
</button>
)
为了调试的目的,我们建议或者命名被包裹的函数,或者传入一个name
作为action
的第一个参数。
NOTE: actions
不会被追踪。
actions
另一个特性是他们不会被追踪。
当从副作用或计算值内部调用action
时(非常罕见!),action
读取的observable
值不会被计入派生的依赖项。
makeAutoObservable
,extendObservable
和observable
使用一种称为autoAction
的特殊动作,它将在运行时确定函数是derivation
还是action
。
action.bound
使用:
-
action.bound (annotation)
action.bound
注解可以用于自动绑定一个方法到正确的实例上(保证this
上下文的引用正确性),以便始终正确的将this
绑定到函数内部。
TIP
相比action.bound
,更喜欢箭头函数(推荐使用箭头函数的方式来绑定上下文)
如果你想要使用makeAutoObservable
结合绑定 actions
, 通常使用箭头函数会更简单。
import { makeAutoObservable } from "mobx"
class Doubler {
value = 0
constructor(value) {
makeAutoObservable(this)
}
increment = () => {
this.value++ ;劳动法非法。
KH
this.value++
}
}
runInAction
用法:
- runInAction(fn)
使用这个辅助方法来创建一个立即执行的临时aciton
,在异步流程中会非常有用。
Asynchronous actions
本质上,在Mobx中异步流程不需要任何特殊的对待,因为所有的reactions
将会自动发生更新,而不管它们在何时被引起的。而且,由于可观察对象是可变的,因此在整个操作过程中保持对它们的引用通常是安全的。然后,在一个异步流程中,更新observable
的每一步都应该被标记为action
。可以通过利用上述API以多种方式实现这一点,如下所示。
例如,当处理promises
的时候,更新state
的程序应该被action
包裹(还记的action可以作为一个函数直接调用吗?)或者应该是一个action
,如下所示:
Wrap handlers in action
Promise resolution handlers are handled in-line, but run after the original action finished, so they need to be wrapped by 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"
})
)
}
}
使用 flow替代 of async / await {🚀}
用法:
- flow (注解)
- flow(function* (args) { }
flow 是async / await的可选替代方案,它使使用MobX操作更加容易。generator function
功能作为其唯一输入。在生成器内部,您可以通过yield somePromse
实现同步链写法(使用yield somePromise
替换wait somePromise
)。然后,flow
将确保生成器在产生的承诺解决时继续运行或抛出。
所有,flow
是async / await
的替代方案,这种方式不需要进一步使用action
包裹。可以按照如下步骤应用:
- 使用
flow
包裹你的异步函数 - 使用
function *
替换async
- 使用
yield
替换await
代码如下:
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
} catch (error) {
this.state = "error"
}
}
}
const store = new Store()
const projects = await flowResult(store.fetchProjects())
需要注意的是,上面例子中的flowResult
函数只有当使用typescript
的时候才需要。
因为使用flow
装饰一个方法,它将会使用一个promise
包裹返回的generator
,但是typescript
并不知道这种转换,所以flowResult
将确保typescript
知道这种类型改变。
makeAutoObservable
将会自动推动generator
函数为flow
。
NOTE 也可以在对象的属性上使用flow
类似于action
, flow
也可以也可以直接包裹一个函数使用,上面的例子也可以是使用如下代码书写。
import { flow } from "mobx"
class Store {
githubProjects = []
state = "pending"
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()
这样写法的好处是,如果你使用typescript
,不再需要flowResult
,缺点是传入this
,确保上下文引用正确。
补充:你可以选择使用bind
来绑定你的上下文。
import { flow } from "mobx"
class Store {
githubProjects = []
state = "pending"
fetchProjects = flow(function* () {
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"
}
})
}.bind(this); // here we use bind.
const store = new Store()
const projects = await store.fetchProjects()
Cancelling flows{🚀}
flow
的另外一个好处是可以取消。flow
的返回值是一个promise
,这个返回值是通过generator
函数的返回值。这个返回的promise
有个额外的cancel()
方法,可以用来打断正在执行的generator
并且取消它。try / finally
的代码将任然会被执行。
Disabling mandatory actions {🚀}
默认,Mobx6以及之后的版本将会要求你使用actions
来改变state
。但是你也可以使用Mobx 配置,禁用这一行为。查看enforceActions
章节。比如,这在单元测试配置中非常有用,单元测试中这些警告没有什么价值。