如何在Mobx里实现一个异步的action?

使用Mobx的时候,store里的状态的改变,默认情况下,必须经过action来操作,这可以让状态修改的操作在代码里面更加明确,也可以更好地组织代码。

对于普通同步的action,这个参考最简单的Mobx例子就可以了。但是对于异步的action,这点需要留意一下。

如果是用Promise,那么then()函数里传入的应该是一个action;如果是用async/await, await之后的代码在运行时实际上仍旧是一个类似callback的一个函数里,因此也需要是一个action。这种情况下,可以有两个选择:

  1. 将callback定义成一个action。
  2. 使用runInAction这个工具函数。

比如:

mobx.configure({ enforceActions: true })

class Store {
    @observable githubProjects = []
    @observable state = "pending" // "pending" / "done" / "error"

    @action
    async fetchProjects() {
        this.githubProjects = []
        this.state = "pending"
        try {
            const projects = await fetchGithubProjectsSomehow()
            const filteredProjects = somePreprocessing(projects)
            // await 之后,再次修改状态需要动作:
            runInAction(() => {
                this.state = "done"
                this.githubProjects = filteredProjects
            })
        } catch (error) {
            runInAction(() => {
                this.state = "error"
            })
        }
    }
}

另外一种更简单的用法呢,则是用flow。flow实际上借助的是generator的语法。虽然generator是个不太常见的词,但是没关系,如果熟悉async/await,那么在实际写代码的时候,把async换成*,把await换成yield就可以了。这样可以让代码更简洁:

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()

调用flow的action的时候,还是可以照样用await来调用的。