创建可观察状态
属性、整个对象、数组、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 以获取一个非代理的克隆。
可用注解
| 注解 | 描述 |
|---|---|
observableobservable.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 对象中,这使它们在调试器中具有更好的体验。 最后,类受益于许多引擎优化,因为它们的形状是可预测的,方法在原型上共享。 但是,繁重的继承模式很容易成为脚枪,因此如果您使用类,请保持简单。 因此,即使存在轻微的偏好使用类,我们也绝对希望鼓励您在更适合您的情况下偏离这种风格。
