MobX 中文网

MobX 中文网

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

›提示与技巧

介绍

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

MobX 核心

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

MobX 与 React

  • React 集成
  • React 优化 {🚀}

提示与技巧

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

微调

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

了解反应性

¥Understanding reactivity

MobX 通常会对你期望的事情做出准确的反应,这意味着在 90% 的用例中 MobX 应该 "只是工作"。然而,在某些时候你会遇到一种情况,它没有达到你的预期。在这一点上,了解 MobX 如何确定对什么做出反应是非常有价值的。

¥MobX usually reacts to exactly the things you expect it to, which means that in 90% of your use cases MobX should "just work". However, at some point you will encounter a case where it does not do what you expected. At that point it is invaluable to understand how MobX determines what to react to.

MobX 对在跟踪函数执行期间读取的任何现有可观察属性做出反应。

¥MobX reacts to any existing observable property that is read during the execution of a tracked function.

  • "reading" 正在取消引用对象的属性,这可以通过 "点入" 它(例如 user.name)或使用括号表示法(例如 user['name']、todos[3])或解构(例如 const {name} = user)来完成。

    ¥"reading" is dereferencing an object's property, which can be done through "dotting into" it (eg. user.name) or using the bracket notation (eg. user['name'], todos[3]) or destructuring (eg. const {name} = user).

  • "跟踪功能" 是 computed 的表达式,observer React 函数组件的渲染,基于 observer 的 React 类组件的 render() 方法,以及作为第一个参数传递给 autorun、reaction 和 when 的函数。

    ¥"tracked functions" are the expression of computed, the rendering of an observer React function component, the render() method of an observer based React class component, and the functions that are passed as the first param to autorun, reaction and when.

  • "during" 表示仅跟踪函数执行时读取的那些可观察值。跟踪函数直接或间接使用这些值并不重要。但函数中 'spawned' 的内容将不会被跟踪(例如 setTimeout、promise.then、await 等)。

    ¥"during" means that only those observables that are read while the function is executing are tracked. It doesn't matter whether these values are used directly or indirectly by the tracked function. But things that have been 'spawned' from the function won't be tracked (e.g. setTimeout, promise.then, await etc).

换句话说,MobX 不会对以下情况做出反应:

¥In other words, MobX will not react to:

  • 从可观察量获取但在跟踪函数之外的值

    ¥Values that are obtained from observables, but outside a tracked function

  • 在异步调用的代码块中读取的可观察量

    ¥Observables that are read in an asynchronously invoked code block

MobX 跟踪属性访问,而不是值

¥MobX tracks property access, not values

为了通过示例来详细说明上述规则,假设你有以下可观察实例:

¥To elaborate on the above rules with an example, suppose that you have the following observable instance:

class Message {
    title
    author
    likes
    constructor(title, author, likes) {
        makeAutoObservable(this)
        this.title = title
        this.author = author
        this.likes = likes
    }

    updateTitle(title) {
        this.title = title
    }
}

let message = new Message("Foo", { name: "Michel" }, ["Joe", "Sara"])

在内存中,这看起来如下。绿色框表示可观察的属性。请注意,值本身是不可观察的!

¥In memory this looks as follows. The green boxes indicate observable properties. Note that the values themselves are not observable!

MobX reacts to changing references

MobX 基本上所做的就是记录你在函数中使用的箭头。之后,只要这些箭头之一发生变化,它就会重新运行;当他们开始提及其他事物时。

¥What MobX basically does is recording which arrows you use in your function. After that, it will re-run whenever one of these arrows changes; when they start to refer to something else.

示例

¥Examples

让我们用一堆例子来展示这一点(基于上面定义的 message 变量):

¥Let's show that with a bunch of examples (based on the message variable defined above):

正确的:跟踪函数内部取消引用

¥Correct: dereference inside the tracked function

autorun(() => {
    console.log(message.title)
})
message.updateTitle("Bar")

这将按预期做出反应。.title 属性由自动运行取消引用,并随后发生更改,因此会检测到此更改。

¥This will react as expected. The .title property was dereferenced by the autorun, and changed afterwards, so this change is detected.

你可以通过在跟踪函数内调用 trace() 来验证 MobX 将跟踪的内容。对于上述函数,它输出以下内容:

¥You can verify what MobX will track by calling trace() inside the tracked function. In the case of the above function it outputs the following:

import { trace } from "mobx"

const disposer = autorun(() => {
    console.log(message.title)
    trace()
})
// Outputs:
// [mobx.trace] 'Autorun@2' tracing enabled

message.updateTitle("Hello")
// Outputs:
// [mobx.trace] 'Autorun@2' is invalidated due to a change in: 'Message@1.title'
Hello

还可以使用 getDependencyTree 获取内部依赖(或观察者)树:

¥It is also possible to get the internal dependency (or observer) tree by using getDependencyTree:

import { getDependencyTree } from "mobx"

// Prints the dependency tree of the reaction coupled to the disposer.
console.log(getDependencyTree(disposer))
// Outputs:
// { name: 'Autorun@2', dependencies: [ { name: 'Message@1.title' } ] }

错误的:改变不可观察的参考

¥Incorrect: changing a non-observable reference

autorun(() => {
    console.log(message.title)
})
message = new Message("Bar", { name: "Martijn" }, ["Felicia", "Marcus"])

这样就不会反应了。message 被更改,但 message 不是可观察的,只是一个引用可观察的变量,但变量(引用)本身不是可观察的。

¥This will not react. message was changed, but message is not an observable, just a variable which refers to an observable, but the variable (reference) itself is not observable.

错误的:在跟踪函数之外取消引用

¥Incorrect: dereference outside of a tracked function

let title = message.title
autorun(() => {
    console.log(title)
})
message.updateMessage("Bar")

这样就不会反应了。message.title 在 autorun 之外取消引用,并且仅包含取消引用时 message.title 的值(字符串 "Foo")。title 不是可观察的,因此 autorun 永远不会做出反应。

¥This will not react. message.title was dereferenced outside of autorun, and just contains the value of message.title at the moment of dereferencing (the string "Foo"). title is not an observable so autorun will never react.

正确的:跟踪函数内部取消引用

¥Correct: dereference inside the tracked function

autorun(() => {
    console.log(message.author.name)
})

runInAction(() => {
    message.author.name = "Sara"
})
runInAction(() => {
    message.author = { name: "Joe" }
})

这对这两种变化都有反应。author 和 author.name 都被点缀进去,允许 MobX 跟踪这些引用。

¥This reacts to both changes. Both author and author.name are dotted into, allowing MobX to track these references.

请注意,我们必须在此处使用 runInAction 才能允许在 action 之外进行更改。

¥Note that we had to use runInAction here to be allowed to make changes outside of an action.

错误的:存储对可观察对象的本地引用而不进行跟踪

¥Incorrect: store a local reference to an observable object without tracking

const author = message.author
autorun(() => {
    console.log(author.name)
})

runInAction(() => {
    message.author.name = "Sara"
})
runInAction(() => {
    message.author = { name: "Joe" }
})

将拾取第一个更改,message.author 和 author 是同一对象,并且 .name 属性在自动运行中取消引用。然而,第二个更改没有被拾取,因为 message.author 关系没有被 autorun 跟踪。Autorun 仍然使用 "old" author。

¥The first change will be picked up, message.author and author are the same object, and the .name property is dereferenced in the autorun. However, the second change is not picked up, because the message.author relation is not tracked by the autorun. Autorun is still using the "old" author.

常见陷阱:console.log

¥Common pitfall: console.log

autorun(() => {
    console.log(message)
})

// Won't trigger a re-run.
message.updateTitle("Hello world")

在上面的示例中,更新的消息标题不会被打印,因为它没有在自动运行中使用。自动运行仅依赖于 message,它不是一个可观察对象,而是一个变量。也就是说,就 MobX 而言,autorun 中并没有使用 title。

¥In the above example, the updated message title won't be printed, because it is not used inside the autorun. The autorun only depends on message, which is not an observable, but a variable. In other words, as far as MobX is concerned, title is not used in the autorun.

如果你在网页浏览器调试工具中使用它,你也许能够找到 title 的更新值,但这是误导性的 - autorun run 毕竟在第一次调用时已经运行过一次。发生这种情况是因为 console.log 是一个异步函数,并且该对象仅在稍后才被格式化。这意味着如果你按照调试工具栏中的标题操作,就可以找到更新后的值。但 autorun 不跟踪任何更新。

¥If you use this in a web browser debugging tool, you may be able to find the updated value of title after all, but this is misleading -- autorun run after all has run once when it was first called. This happens because console.log is an asynchronous function and the object is only formatted later in time. This means that if you follow the title in the debugging toolbar, you can find the updated value. But the autorun does not track any updates.

实现此目的的方法是确保始终将不可变数据或防御副本传递给 console.log。因此以下解决方案都对 message.title 中的更改做出反应:

¥The way to make this work is to make sure to always pass immutable data or defensive copies to console.log. So the following solutions all react to changes in message.title:

autorun(() => {
    console.log(message.title) // Clearly, the `.title` observable is used.
})

autorun(() => {
    console.log(mobx.toJS(message)) // toJS creates a deep clone, and thus will read the message.
})

autorun(() => {
    console.log({ ...message }) // Creates a shallow clone, also using `.title` in the process.
})

autorun(() => {
    console.log(JSON.stringify(message)) // Also reads the entire structure.
})

正确的:访问跟踪函数中的数组属性

¥Correct: access array properties in tracked function

autorun(() => {
    console.log(message.likes.length)
})
message.likes.push("Jennifer")

这将按预期做出反应。.length 计入属性。请注意,这将对数组中的任何更改做出反应。数组不是按索引/属性(如可观察对象和映射)进行跟踪,而是作为一个整体进行跟踪。

¥This will react as expected. .length counts towards a property. Note that this will react to any change in the array. Arrays are not tracked per index / property (like observable objects and maps), but as a whole.

错误的:访问跟踪函数中的越界索引

¥Incorrect: access out-of-bounds indices in tracked function

autorun(() => {
    console.log(message.likes[0])
})
message.likes.push("Jennifer")

这将与上面的示例数据发生反应,因为数组索引算作属性访问。但前提是提供了 index < length。MobX 不跟踪尚不存在的数组索引。因此,请始终使用 .length 检查来保护基于数组索引的访问。

¥This will react with the above sample data because array indexes count as property access. But only if the provided index < length. MobX does not track not-yet-existing array indices. So always guard your array index based access with a .length check.

正确的:访问跟踪函数中的数组函数

¥Correct: access array functions in tracked function

autorun(() => {
    console.log(message.likes.join(", "))
})
message.likes.push("Jennifer")

这将按预期做出反应。所有不会改变数组的数组函数都会被自动跟踪。

¥This will react as expected. All array functions that do not mutate the array are tracked automatically.


autorun(() => {
    console.log(message.likes.join(", "))
})
message.likes[2] = "Jennifer"

这将按预期做出反应。检测所有数组索引分配,但仅当 index <= length。

¥This will react as expected. All array index assignments are detected, but only if index <= length.

错误的:"use" 是可观察的,但不访问其任何属性

¥Incorrect: "use" an observable but without accessing any of its properties

autorun(() => {
    message.likes
})
message.likes.push("Jennifer")

这样就不会反应了。原因很简单,因为 likes 数组本身并未被 autorun 使用,仅使用对该数组的引用。因此相比之下,message.likes = ["Jennifer"] 将被拾取;该语句不会修改数组,而是修改 likes 属性本身。

¥This will not react. Simply because the likes array itself is not being used by the autorun, only the reference to the array. So in contrast, message.likes = ["Jennifer"] would be picked up; that statement does not modify the array, but the likes property itself.

正确的:使用尚不存在的映射条目

¥Correct: using not yet existing map entries

const twitterUrls = observable.map({
    Joe: "twitter.com/joey"
})

autorun(() => {
    console.log(twitterUrls.get("Sara"))
})

runInAction(() => {
    twitterUrls.set("Sara", "twitter.com/horsejs")
})

这样就会有反应了。可观察映射支持观察可能不存在的条目。请注意,这最初将打印 undefined。你可以首先使用 twitterUrls.has("Sara") 检查条目是否存在。因此,在没有代理支持动态键控集合的环境中,始终使用可观察映射。如果你确实有代理支持,你也可以使用可观察的映射,但你也可以选择使用普通对象。

¥This will react. Observable maps support observing entries that may not exist. Note that this will initially print undefined. You can check for the existence of an entry first by using twitterUrls.has("Sara"). So in an environment without Proxy support for dynamically keyed collections always use observable maps. If you do have Proxy support you can use observable maps as well, but you also have the option to use plain objects.

MobX 不跟踪异步访问的数据

¥MobX does not track asynchronously accessed data

function upperCaseAuthorName(author) {
    const baseName = author.name
    return baseName.toUpperCase()
}
autorun(() => {
    console.log(upperCaseAuthorName(message.author))
})

runInAction(() => {
    message.author.name = "Chesterton"
})

这样就会有反应了。即使 author.name 没有被传递给 autorun 本身的函数解除引用,MobX 仍然会跟踪 upperCaseAuthorName 中发生的解除引用,因为它发生在自动运行执行期间。

¥This will react. Even though author.name is not dereferenced by the function passed to autorun itself, MobX will still track the dereferencing that happens in upperCaseAuthorName, because it happens during the execution of the autorun.


autorun(() => {
    setTimeout(() => console.log(message.likes.join(", ")), 10)
})

runInAction(() => {
    message.likes.push("Jennifer")
})

这不会做出反应,因为在执行 autorun 期间没有访问任何可观察对象,仅在 setTimeout 期间访问,这是一个异步函数。

¥This will not react because during the execution of the autorun no observables were accessed, only during the setTimeout, which is an asynchronous function.

另请查看 异步操作 部分。

¥Check out the Asynchronous actions section as well.

使用不可观察的对象属性

¥Using non-observable object properties

autorun(() => {
    console.log(message.author.age)
})

runInAction(() => {
    message.author.age = 10
})

如果你在支持 Proxy 的环境中运行 React,这将会做出反应。请注意,这仅适用于使用 observable 或 observable.object 创建的对象。类实例上的新属性不会自动变得可观察。

¥This will react if you run React in an environment that supports Proxy. Note that this is only done for objects created with observable or observable.object. New properties on class instances will not be made observable automatically.

没有代理支持的环境

¥Environments without Proxy support

这样就不会反应了。MobX 只能跟踪 observable 属性,而 'age' 还没有被定义为上面的 observable 属性。

¥This will not react. MobX can only track observable properties, and 'age' has not been defined as observable property above.

但是,可以使用 MobX 公开的 get 和 set 方法来解决此问题:

¥However, it is possible to use the get and set methods as exposed by MobX to work around this:

import { get, set } from "mobx"

autorun(() => {
    console.log(get(message.author, "age"))
})
set(message.author, "age", 10)

[无代理支持] 错误:使用尚不存在的可观察对象属性

¥[Without Proxy support] Incorrect: using not yet existing observable object properties

autorun(() => {
    console.log(message.author.age)
})
extendObservable(message.author, {
    age: 10
})

这样就不会反应了。MobX 不会对跟踪开始时不存在的可观察属性做出反应。如果两个语句交换,或者任何其他可观察导致 autorun 重新运行,则 autorun 也将开始跟踪 age。

¥This will not react. MobX will not react to observable properties that did not exist when tracking started. If the two statements are swapped, or if any other observable causes the autorun to re-run, the autorun will start tracking the age as well.

[无代理支持] 正确:使用 MobX 工具读取/写入对象

¥[Without Proxy support] Correct: using MobX utilities to read / write to objects

如果你处于没有代理支持的环境中,并且仍然希望将可观察对象用作动态集合,则可以使用 MobX get 和 set API 来处理它们。

¥If you are in an environment without proxy support and still want to use observable objects as a dynamic collection, you can handle them using the MobX get and set API.

以下也会做出反应:

¥The following will react as well:

import { get, set, observable } from "mobx"

const twitterUrls = observable.object({
    Joe: "twitter.com/joey"
})

autorun(() => {
    console.log(get(twitterUrls, "Sara")) // `get` can track not yet existing properties.
})

runInAction(() => {
    set(twitterUrls, { Sara: "twitter.com/horsejs" })
})

查看 收集实用程序 API 了解更多详细信息。

¥Check out the Collection utilities API for more details.

TL;DR

MobX 对在跟踪函数执行期间读取的任何现有可观察属性做出反应。

¥MobX reacts to any existing observable property that is read during the execution of a tracked function.

← 定义数据存储子类化 →
  • MobX 跟踪属性访问,而不是值
  • 示例
MobX v6.13 中文网 - 粤ICP备13048890号
Nodejs.cn 旗下网站