MobX 中文网

MobX 中文网

  • API 文档
  • Nodejs.cn 旗下网站

›MobX 核心

介绍

  • 关于 MobX
  • 关于本文档
  • 安装
  • MobX 的要点

MobX 核心

  • 可观察状态
  • 操作
  • 计算
  • 反应 {🚀}
  • API

MobX 与 React

  • React 集成
  • React 优化 {🚀}

提示与技巧

  • 定义数据存储
  • 了解反应性
  • 子类化
  • 分析反应性 {🚀}
  • 使用参数进行计算 {🚀}
  • MobX-utils {🚀}
  • 自定义可观察值 {🚀}
  • 惰性可观察量 {🚀}
  • 集合实用程序 {🚀}
  • 拦截和观察 {🚀}

微调

  • 配置 {🚀}
  • 装饰器 {🚀}
  • 从 MobX 4/5 迁移 {🚀}

使用操作更新状态

¥Updating state using actions

用法:

¥Usage:

  • action(注解)

    ¥action (annotation)

  • action(fn)

  • action(name, fn)

  • @action(方法/字段装饰器)

    ¥@action (method / field decorator)

所有应用都有操作。操作是修改状态的任何代码段。原则上,动作总是响应事件而发生。例如,单击按钮、更改某些输入、到达 Websocket 消息等。

¥All applications have actions. An action is any piece of code that modifies the state. In principle, actions always happen in response to an event. For example, a button was clicked, some input changed, a websocket message arrived, etc.

MobX 要求你声明你的操作,尽管 makeAutoObservable 可以自动化大部分这项工作。操作可帮助你更好地构建代码并提供以下性能优势:

¥MobX requires that you declare your actions, although makeAutoObservable can automate much of this job. Actions help you structure your code better and offer the following performance benefits:

  1. 它们在 transactions 内运行。在最外层操作完成之前,不会运行任何反应,从而保证操作期间生成的中间值或不完整值在操作完成之前对应用的其余部分不可见。

    ¥They are run inside transactions. No reactions will be run until the outer-most action has finished, guaranteeing that intermediate or incomplete values produced during an action are not visible to the rest of the application until the action has completed.

  2. 默认情况下,不允许在操作之外更改状态。这有助于在代码库中清楚地识别状态更新发生的位置。

    ¥By default, it is not allowed to change the state outside of actions. This helps to clearly identify in your code base where the state updates happen.

action 注释只能用在打算修改状态的函数上。派生信息(执行查找或过滤数据)的函数不应标记为操作,以允许 MobX 跟踪其调用。action 带注释的成员将是不可枚举的。

¥The action annotation should only be used on functions that intend to modify the state. Functions that derive information (performing lookups or filtering data) should not be marked as actions, to allow MobX to track their invocations. action annotated members will be non-enumerable.

示例

¥Examples

makeObservable
@action
makeAutoObservable
action.bound
action(fn)
runInAction(fn)
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 封装函数

¥Wrapping functions using action

为了尽可能地利用 MobX 的事务性质,操作应该尽可能向外传递。如果类方法修改了状态,最好将其标记为操作。最好将事件处理程序标记为操作,因为最外层的事务才重要。随后调用两个操作的单个未标记事件处理程序仍会生成两个事务。

¥To leverage the transactional nature of MobX as much as possible, actions should be passed as far outward as possible. It is good to mark a class method as an action if it modifies the state. It is even better to mark event handlers as actions, as it is the outer-most transaction that counts. A single unmarked event handler that calls two actions subsequently would still generate two transactions.

为了帮助创建基于操作的事件处理程序,action 不仅是一个注释,而且还是一个高阶函数。可以使用函数作为参数来调用它,在这种情况下,它将返回具有相同签名的 action 封装函数。

¥To help create action based event handlers, action is not only an annotation, but also a higher order function. It can be called with a function as an argument, and in that case it will return an action wrapped function with the same signature.

例如,在 React 中,可以按如下方式封装 onClick 处理程序。

¥For example in React, an onClick handler can be wrapped as below.

const ResetButton = ({ formState }) => (
    <button
        onClick={action(e => {
            formState.resetPendingUploads()
            formState.resetValues()
            e.preventDefault()
        })}
    >
        Reset form
    </button>
)

出于调试目的,我们建议为封装的函数命名,或者将名称作为第一个参数传递给 action。

¥For debugging purposes, we recommend to either name the wrapped function, or pass a name as the first argument to action.

Note: actions are untracked

动作的另一个特点是它们是 untracked。当从副作用或计算值内部调用操作时(非常罕见!),操作读取的可观察量将不计入派生的依赖

¥Another feature of actions is that they are untracked. When an action is called from inside a side effect or a computed value (very rare!), observables read by the action won't be counted towards the dependencies of the derivation

makeAutoObservable、extendObservable 和 observable 使用 action 的一种特殊风格,称为 autoAction,它将在运行时确定该函数是派生函数还是动作。

¥makeAutoObservable, extendObservable and observable use a special flavour of action called autoAction, that will determine at runtime if the function is a derivation or action.

action.bound

用法:

¥Usage:

  • action.bound(注解)

    ¥action.bound (annotation)

action.bound 注释可用于自动将方法绑定到正确的实例,以便 this 始终正确绑定在函数内部。

¥The action.bound annotation can be used to automatically bind a method to the correct instance, so that this is always correctly bound inside the function.

Tip: use makeAutoObservable(o, {}, { autoBind: true }) to bind all actions and flows automatically

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

用法:

¥Usage:

  • runInAction(fn)

使用此实用程序创建立即调用的临时操作。在异步进程中很有用。查看 上面的代码块 的示例。

¥Use this utility to create a temporary action that is immediately invoked. Can be useful in asynchronous processes. Check out the above code block for an example.

动作和继承

¥Actions and inheritance

只有在原型上定义的操作才能被子类覆盖:

¥Only actions defined on prototype can be overridden by subclass:

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,
        })
    }
}

要将单个操作绑定到 this,可以使用 action.bound 代替箭头函数。
有关详细信息,请参阅 subclassing。

¥To bind a single action to this, action.bound can be used instead of arrow functions.
See subclassing for more information.

异步操作

¥Asynchronous actions

本质上,异步进程在 MobX 中不需要任何特殊处理,因为所有反应都会自动更新,无论它们是在何时引起的。由于可观察对象是可变的,因此在操作期间保留对它们的引用通常是安全的。但是,在异步过程中更新可观察量的每个步骤(勾选)都应标记为 action。这可以通过利用上述 API 以多种方式实现,如下所示。

¥In essence, asynchronous processes don't need any special treatment in MobX, as all reactions will update automatically regardless of the moment in time they are caused. And since observable objects are mutable, it is generally safe to keep references to them for the duration of an action. However, every step (tick) that updates observables in an asynchronous process should be marked as action. This can be achieved in multiple ways by leveraging the above APIs, as shown below.

例如,在处理 Promise 时,更新状态的处理程序应该是操作,或者应该使用 action 封装,如下所示。

¥For example, when handling promises, the handlers that update state should be actions or should be wrapped using action, as shown below.

Wrap handlers in `action`
Handle updates in separate actions
async/await + runInAction
`flow` + generator function

Promise 解析处理程序是内联处理的,但在原始操作完成后运行,因此它们需要由 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"
})
)
}
}

如果 Promise 处理程序是类字段,它们将自动被 makeAutoObservable 封装在 action 中:

¥If the promise handlers are class fields, they will automatically be wrapped in action by makeAutoObservable:

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 之后的任何步骤都不在同一个刻度中,因此它们需要操作封装。在这里,我们可以利用 runInAction:

¥Any steps after await aren't in the same tick, so they require action wrapping. Here, we can leverage 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 {🚀}

¥Using flow instead of async / await {🚀}

用法:

¥Usage:

  • flow(注解)

    ¥flow (annotation)

  • flow(function* (args) { })

  • @flow(方法装饰器)

    ¥@flow (method decorator)

flow 封装器是 async / await 的可选替代品,可以更轻松地使用 MobX 操作。flow 将 生成器函数 作为其唯一输入。在生成器内部,你可以通过生成 Promise 来链接它们(而不是编写 await somePromise,而是编写 yield somePromise)。然后,流机制将确保生成器在生成的 Promise 解析时继续或抛出。

¥The flow wrapper is an optional alternative to async / await that makes it easier to work with MobX actions. flow takes a generator function as its only input. Inside the generator, you can chain promises by yielding them (instead of await somePromise you write yield somePromise). The flow mechanism will then make sure the generator either continues or throws when a yielded promise resolves.

因此 flow 是 async / await 的替代品,不需要任何进一步的 action 封装。它可以按如下方式应用:

¥So flow is an alternative to async / await that doesn't need any further action wrapping. It can be applied as follows:

  1. 将 flow 封装在你的异步函数中。

    ¥Wrap flow around your asynchronous function.

  2. 使用 function * 代替 async。

    ¥Instead of async use function *.

  3. 使用 yield 代替 await。

    ¥Instead of await use yield.

上面的 flow + 生成器函数 示例展示了实际情况。

¥The flow + generator function example above shows what this looks like in practice.

请注意,仅在使用 TypeScript 时才需要 flowResult 函数。由于用 flow 修饰一个方法,它会将返回的生成器封装在一个 promise 中。但是,TypeScript 不知道该转换,因此 flowResult 将确保 TypeScript 知道该类型更改。

¥Note that the flowResult function is only needed when using TypeScript. Since decorating a method with flow, it will wrap the returned generator in a promise. However, TypeScript isn't aware of that transformation, so flowResult will make sure that TypeScript is aware of that type change.

makeAutoObservable 和朋友会自动推断生成器为 flow。flow 带注释的成员将是不可枚举的。

¥makeAutoObservable and friends will automatically infer generators to be flows. flow annotated members will be non-enumerable.

{🚀} Note: using flow on object fields flow, like action, can be used to wrap functions directly. The above example could also have been written as follows:

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 以确保正确推断其类型。

¥The upside is that we don't need flowResult anymore, the downside is that this needs to be typed to make sure its type is inferred correctly.

flow.bound

用法:

¥Usage:

  • flow.bound(注解)

    ¥flow.bound (annotation)

flow.bound 注释可用于自动将方法绑定到正确的实例,以便 this 始终正确绑定在函数内部。与操作类似,默认情况下可以使用 autoBind 选项 绑定流。

¥The flow.bound annotation can be used to automatically bind a method to the correct instance, so that this is always correctly bound inside the function. Similary to actions, flows can be bound by default using autoBind option.

取消流 {🚀}

¥Cancelling flows {🚀}

流的另一个好处是它们是可以取消的。flow 的返回值是一个 Promise,它最终使用生成器函数返回的值进行解析。返回的 Promise 有一个额外的 cancel() 方法,该方法将中断正在运行的生成器并取消它。任何 try / finally 子句仍将运行。

¥Another neat benefit of flows is that they are cancellable. The return value of flow is a promise that resolves with the value that is returned from the generator function in the end. The returned promise has an additional cancel() method that will interrupt the running generator and cancel it. Any try / finally clauses will still be run.

禁用强制操作 {🚀}

¥Disabling mandatory actions {🚀}

默认情况下,MobX 6 及更高版本要求你使用操作来更改状态。但是,你可以配置 MobX 来禁用此行为。查看 enforceActions 部分。例如,这在单元测试设置中非常有用,其中警告并不总是有多大价值。

¥By default, MobX 6 and later require that you use actions to make changes to the state. However, you can configure MobX to disable this behavior. Check out the enforceActions section. For example, this can be quite useful in unit test setup, where the warnings don't always have much value.

← 可观察状态计算 →
  • 示例
  • 使用 action 封装函数
  • action.bound
  • runInAction
  • 动作和继承
  • 异步操作
  • 使用 flow 代替 async/await {🚀}
  • flow.bound
  • 取消流 {🚀}
  • 禁用强制操作 {🚀}
MobX v6.13 中文网 - 粤ICP备13048890号
Nodejs.cn 旗下网站