使用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
章节。比如,这在单元测试配置中非常有用,单元测试中这些警告没有什么价值。