创建可观察状态
属性、整个对象、数组、Map 和 Set 都可以被设为可观察的。使对象可观察的基本方法是使用 makeObservable
为每个属性指定一个注解。最重要的注解是
observable
定义了一个可跟踪的字段,用于存储状态。action
将一个方法标记为一个会修改状态的动作。computed
将一个 getter 标记为一个从状态中推导出新事实并缓存其输出的 getter。
makeObservable
用法
makeObservable(target, annotations?, options?)
此函数可用于使现有对象属性可观察。任何 JavaScript 对象(包括类实例)都可以传递给 target
。通常,makeObservable
在类的构造函数中使用,其第一个参数是 this
。annotations
参数将 注解 映射到每个成员。只有被注解的成员会被影响。
或者,像 @observable
这样的装饰器可以用在类成员上,而不是在构造函数中调用 makeObservable
。
推导信息并接受参数(例如 findUsersOlderThan(age: number): User[]
)的方法不能被注解为 computed
- 当它们从反应中被调用时,它们的读取操作仍然会被跟踪,但它们的输出不会被记忆,以避免内存泄漏。要记忆这样的方法,可以使用 MobX-utils computedFn {🚀} 代替。
子类化在使用 override
注解的情况下得到支持,但也有一些限制(请参阅 此处 的示例)。
import { makeObservable, observable, computed, action, flow } from "mobx"
class Doubler {
value
constructor(value) {
makeObservable(this, {
value: observable,
double: computed,
increment: action,
fetch: flow
})
this.value = value
}
get double() {
return this.value * 2
}
increment() {
this.value++
}
*fetch() {
const response = yield fetch("/api/value")
this.value = response.json()
}
}
所有被注解的 字段都是 不可配置的。
所有不可观察的(无状态)字段(action
、flow
)都是 不可写的。
当使用现代装饰器时,无需调用 makeObservable
,下面是一个基于装饰器的类的示例。请注意,@observable
注解应始终与 accessor
关键字一起使用。
import { observable, computed, action, flow } from "mobx"
class Doubler {
@observable accessor value
constructor(value) {
this.value = value
}
@computed
get double() {
return this.value * 2
}
@action
increment() {
this.value++
}
@flow
*fetch() {
const response = yield fetch("/api/value")
this.value = response.json()
}
}
import { makeAutoObservable } from "mobx"
function createDoubler(value) {
return makeAutoObservable({
value,
get double() {
return this.value * 2
},
increment() {
this.value++
}
})
}
请注意,类也可以利用 makeAutoObservable
。示例之间的区别仅仅说明了 MobX 如何应用于不同的编程风格。
import { observable } from "mobx"
const todosById = observable({
"TODO-123": {
title: "find a decent task management system",
done: false
}
})
todosById["TODO-456"] = {
title: "close all tickets older than two weeks",
done: true
}
const tags = observable(["high prio", "medium prio", "low prio"])
tags.push("prio: for fun")
与第一个使用 makeObservable
的示例相比,observable
支持向对象添加(和删除)字段。这使得 observable
非常适合像动态键对象、数组、Map 和 Set 这样的集合。
要使用遗留装饰器,应在构造函数中调用 makeObservable(this)
,以确保装饰器正常工作。
import { observable, computed, action, flow } from "mobx"
class Doubler {
@observable value
constructor(value) {
makeObservable(this)
this.value = value
}
@computed
get double() {
return this.value * 2
}
@action
increment() {
this.value++
}
@flow
*fetch() {
const response = yield fetch("/api/value")
this.value = response.json()
}
}
makeAutoObservable
用法
makeAutoObservable(target, overrides?, options?)
makeAutoObservable
就像 makeObservable
的加强版,因为它默认情况下会推断所有属性。但是,您可以使用 overrides
参数来覆盖默认行为,使用特定的注解 - 特别是 false
可以用来完全排除一个属性或方法。查看上面的代码块以了解示例。
makeAutoObservable
函数可能比使用 makeObservable
更简洁,更容易维护,因为新成员无需显式提及。但是,makeAutoObservable
不能用在有超类或 子类 的类上。
推断规则
- 所有自身属性都变成
observable
。 - 所有
getters
都变成computed
。 - 所有
setters
都变成action
。 - 所有函数都变成
autoAction
。 - 所有生成器函数都变成
flow
。(请注意,在某些编译器配置中,生成器函数是不可检测的,如果 flow 不能按预期工作,请确保显式指定flow
。) - 在
overrides
参数中用false
标记的成员不会被注解。例如,使用它来标识只读字段,例如标识符。
observable
用法
observable(source, overrides?, options?)
@observable accessor
(字段装饰器)
observable
注解也可以作为一个函数来调用,以一次性使整个对象可观察。source
对象将被克隆,所有成员都将被设为可观察的,类似于 makeAutoObservable
的操作方式。同样,也可以提供一个 overrides
地图来指定特定成员的注解。查看上面的代码块以了解示例。
observable
返回的对象将是一个代理,这意味着以后添加到对象中的属性也会被拾取并设为可观察的(除非 代理使用 被禁用)。
observable
方法也可以与 数组、Map 和 Set 这样的集合类型一起调用。它们也会被克隆并转换为其可观察的对应物。
示例: 可观察的数组
以下示例创建了一个可观察的数组,并使用 autorun
观察它。使用 Map 和 Set 集合的工作方式类似。
import { observable, autorun } from "mobx"
const todos = observable([
{ title: "Spoil tea", completed: true },
{ title: "Make coffee", completed: false }
])
autorun(() => {
console.log(
"Remaining:",
todos
.filter(todo => !todo.completed)
.map(todo => todo.title)
.join(", ")
)
})
// Prints: 'Remaining: Make coffee'
todos[0].completed = false
// Prints: 'Remaining: Spoil tea, Make coffee'
todos[2] = { title: "Take a nap", completed: false }
// Prints: 'Remaining: Spoil tea, Make coffee, Take a nap'
todos.shift()
// Prints: 'Remaining: Make coffee, Take a nap'
可观察的数组有一些额外的实用功能
clear()
从数组中删除所有当前条目。replace(newItems)
用新的条目替换数组中所有现有的条目。remove(value)
从数组中按值删除单个项目。如果项目被找到并删除,则返回true
。
注意: 基本类型和类实例永远不会被转换为可观察的
基本类型的值不能被 MobX 设为可观察的,因为它们在 JavaScript 中是不可变的(但它们可以被 装箱)。虽然在库之外通常没有使用这种机制的必要。
类实例永远不会通过将它们传递给 observable
或将它们分配给 observable
属性来被自动设为可观察的。使类成员可观察被认为是类构造函数的责任。
{🚀} 技巧: 可观察的(代理的)与 makeObservable(非代理的)
make(Auto)Observable
和 observable
之间的区别在于,第一个修改了你作为第一个参数传入的对象,而 observable
创建了一个被设为可观察的克隆。
第二个区别是 observable
创建了一个 Proxy
对象,以便能够在使用该对象作为动态查找映射时捕获未来的属性添加。如果你想要设为可观察的对象具有一个固定的结构,所有成员都是预先知道的,我们建议使用 makeObservable
,因为非代理对象的速度稍快,并且它们在调试器和 console.log
中更容易检查。
因此,make(Auto)Observable
是在工厂函数中使用的推荐 API。请注意,可以将 { proxy: false }
作为选项传递给 observable
以获取一个非代理的克隆。
可用注解
注解 | 描述 |
---|---|
observable observable.deep | 定义一个可跟踪的字段,用于存储状态。如果可能,分配给 observable 的任何值都会自动转换为(深度)observable 、autoAction 或 flow ,具体取决于其类型。只有 普通对象 、数组 、Map 、Set 、函数 、生成器函数 是可转换的。类实例和其他类型保持不变。 |
observable.ref | 与 observable 相似,但只有重新分配会被跟踪。分配的值完全被忽略,并且不会自动转换为 observable /autoAction /flow 。例如,如果你打算在可观察的字段中存储不可变数据,请使用此选项。 |
observable.shallow | 与 observable.ref 相似,但适用于集合。任何分配的集合都将被设为可观察的,但集合本身的内容不会变为可观察的。 |
observable.struct | 与 observable 相似,只是任何与当前值在结构上相等的分配值都将被忽略。 |
action | 将方法标记为将修改状态的操作。 查看 操作 了解更多信息。 不可写。 |
action.bound | 与 action 类似,但还会将操作绑定到实例,以便 this 始终被设置。 不可写。 |
computed | 可以在 getter 上使用,将其声明为可以缓存的派生值。 查看 计算属性 了解更多信息。 |
computed.struct | 与 computed 类似,不同之处在于,如果重新计算结果在结构上与先前结果相同,则不会通知任何观察者。 |
true | 推断最佳注释。 查看 makeAutoObservable 了解更多信息。 |
false | 明确不注释此属性。 |
flow | 创建一个 flow 来管理异步进程。 查看 flow 了解更多信息。 请注意,TypeScript 中推断的返回类型可能不正确。 不可写。 |
flow.bound | 与 flow 类似,但还会将 flow 绑定到实例,以便 this 始终被设置。 不可写。 |
override | 适用于子类覆盖的继承的 action 、flow 、computed 、action.bound . |
autoAction | 不应显式使用,但由 makeAutoObservable 在后台使用,以标记可以根据其调用上下文充当操作或派生的方法。 它将在运行时确定函数是派生还是操作。 |
限制
make(Auto)Observable
仅支持已定义的属性。 确保您的 编译器配置 正确,或者作为变通方法,在使用make(Auto)Observable
之前为所有属性分配一个值。 如果配置不正确,将不会正确获取声明但未初始化的字段(如class X { y; }
中的字段)。makeObservable
只能注释其自身类定义声明的属性。 如果子类或超类引入了可观察字段,则必须为此类属性自己调用makeObservable
。options
参数只能提供一次。 传递的options
是“粘性” 的,不能在以后(例如在 子类 中)更改。- 每个字段只能注释一次(
override
除外)。 字段注释或配置不能在 子类 中更改。 - 所有注释 的非纯对象(类)字段都是不可配置的。
可以通过configure({ safeDescriptors: false })
{🚀☣️} 禁用。. - 所有不可观察的(无状态)字段(
action
、flow
)都是 不可写的。
可以通过configure({ safeDescriptors: false })
{🚀☣️} 禁用。. - 只有在原型 上定义的
action
、computed
、flow
、action.bound
可以被子类覆盖. - 默认情况下,TypeScript 不会允许您注释私有字段。 这可以通过显式地将相关的私有字段作为泛型参数传递来克服,例如:
makeObservable<MyStore, "privateField" | "privateField2">(this, { privateField: observable, privateField2: observable })
- 调用
make(Auto)Observable
并提供注释必须无条件进行,因为这使得缓存推断结果成为可能。 - 在调用
make(Auto)Observable
后修改原型是不支持的。 - EcmaScript 私有字段(
#field
)是不支持的。 使用 TypeScript 时,建议使用private
修饰符。 - 混合注释和装饰器 在单个继承链中是不支持的 - 例如,您不能对超类使用装饰器,对子类使用注释。
makeObservable
、extendObservable
不能用于其他内置可观察类型(ObservableMap
、ObservableSet
、ObservableArray
等)makeObservable(Object.create(prototype))
将属性从prototype
复制到创建的对象,并使其observable
。 此行为是错误的,出乎意料的,因此已弃用,并且很可能在将来的版本中发生变化。 不要依赖它。
选项 {🚀}
上述 API 接受一个可选的 options
参数,它是一个支持以下选项的对象
autoBind: true
默认情况下使用action.bound
/flow.bound
,而不是action
/flow
。 不影响显式注释的成员。deep: false
默认情况下使用observable.ref
,而不是observable
。 不影响显式注释的成员。name: <string>
给对象一个调试名称,该名称在错误消息和反射 API 中打印。proxy: false
强制observable(thing)
使用非-代理 实现。 如果对象的形状不会随着时间的推移而改变,这是一个不错的选择,因为非代理对象更容易调试并且速度更快。 此选项不可用于make(Auto)Observable
,请参阅 避免代理。
注意: 选项是粘性 的,只能提供一次
options
参数只能为尚未可观察的 target
提供。一旦可观察对象被初始化,就无法更改选项。
选项存储在目标上,并受后续
makeObservable
/extendObservable
调用的尊重。您不能在 子类 中传递不同的选项。
将可观察对象转换回普通 JavaScript 集合
有时需要将可观察数据结构转换回其普通对应物。 例如,将可观察对象传递给无法跟踪可观察对象的 React 组件时,或者获取不应进一步修改的克隆时。
要浅层地转换集合,可以使用通常的 JavaScript 机制
const plainObject = { ...observableObject }
const plainArray = observableArray.slice()
const plainMap = new Map(observableMap)
要将数据树递归地转换为普通对象,可以使用 toJS
实用程序。 对于类,建议实现 toJSON()
方法,因为它将被 JSON.stringify
拾取。
关于类的简短说明
到目前为止,上面大多数示例都倾向于类语法。 原则上,MobX 对此没有意见,可能也有同样多的 MobX 用户使用普通对象。 但是,类的轻微优势在于它们具有更容易发现的 API,例如 TypeScript。 此外,instanceof
检查对于类型推断非常有用,类实例不会被包装在 Proxy
对象中,这使它们在调试器中具有更好的体验。 最后,类受益于许多引擎优化,因为它们的形状是可预测的,方法在原型上共享。 但是,繁重的继承模式很容易成为脚枪,因此如果您使用类,请保持简单。 因此,即使存在轻微的偏好使用类,我们也绝对希望鼓励您在更适合您的情况下偏离这种风格。