创建可观察的状态
¥Creating observable state
属性、整个对象、数组、映射和集合都可以被观察。使对象可观察的基础是使用 makeObservable
为每个属性指定一个注释。最重要的注释是:
¥Properties, entire objects, arrays, Maps and Sets can all be made observable.
The basics of making objects observable is specifying an annotation per property using makeObservable
.
The most important annotations are:
observable
定义了一个存储状态的可跟踪字段。¥
observable
defines a trackable field that stores the state.action
将方法标记为将修改状态的操作。¥
action
marks a method as an action that will modify the state.computed
标记一个 getter,它将从状态中导出新事实并缓存其输出。¥
computed
marks a getter that will derive new facts from the state and cache its output.
makeObservable
用法:
¥Usage:
makeObservable(target, annotations?, options?)
该函数可用于使现有对象属性可观察。任何 JavaScript 对象(包括类实例)都可以传递到 target
。通常 makeObservable
用于类的构造函数,其第一个参数是 this
。annotations
参数将 annotations 映射到每个成员。只有带注释的成员才会受到影响。
¥This function can be used to make existing object properties observable. Any JavaScript object (including class instances) can be passed into target
.
Typically makeObservable
is used in the constructor of a class, and its first argument is this
.
The annotations
argument maps annotations to each member. Only annotated members are affected.
或者,可以在类成员上使用像 @observable
这样的装饰器,而不是在构造函数中调用 makeObservable
。
¥Alternatively, decorators like @observable
can be used on class members instead of calling makeObservable
in the constructor.
导出信息并接受参数的方法(例如 findUsersOlderThan(age: number): User[]
)不能注释为 computed
– 当从反应中调用它们时,它们的读取操作仍将被跟踪,但它们的输出不会被记忆以避免内存泄漏。要记住这些方法,你可以使用 MobX-utils 计算 Fn {🚀} 代替。
¥Methods that derive information and take arguments (for example findUsersOlderThan(age: number): User[]
) can not be annotated as computed
– their read operations will still be tracked when they are called from a reaction, but their output won't be memoized to avoid memory leaks. To memoize such methods you can use MobX-utils computedFn {🚀} instead.
支持子类化,但有一些限制 通过使用 override
注释(参见示例 此处)。
¥Subclassing is supported with some limitations by using the override
annotation (see the example here).
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
)都是不可写的。
¥All annotated fields are non-configurable.
All non-observable (stateless) fields (action
, flow
) are non-writable.
使用现代装饰器时,无需调用 makeObservable
,下面是基于装饰器的类的样子。请注意,@observable
注释应始终与 accessor
关键字结合使用。
¥When using modern decorators, there is no need to call makeObservable
, below is what a decorator based class looks like.
Note that the @observable
annotation should always be used in combination with the accessor
keyword.
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 如何应用于不同的编程风格。
¥Note that classes can leverage makeAutoObservable
as well.
The difference in the examples just demonstrate how MobX can be applied to different programming styles.
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
非常适合动态键控对象、数组、映射和集合等集合。
¥In contrast to the first example with makeObservable
, observable
supports adding (and removing) fields to an object.
This makes observable
great for collections like dynamically keyed objects, arrays, Maps and Sets.
要使用旧版装饰器,应在构造函数中调用 makeObservable(this)
以确保装饰器正常工作。
¥To use legacy decorators, makeObservable(this)
should be called in the constructor to make sure decorators work.
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
用法:
¥Usage:
makeAutoObservable(target, overrides?, options?)
makeAutoObservable
就像加强版的 makeObservable
,因为它默认推断出所有属性。但是,你可以使用 overrides
参数来覆盖具有特定注释的默认行为 - 特别是 false
可用于完全排除属性或方法的处理。查看上面的代码作为示例。
¥makeAutoObservable
is like makeObservable
on steroids, as it infers all the properties by default. You can however use the overrides
parameter to override the default behavior with specific annotations —
in particular false
can be used to exclude a property or method from being processed entirely.
Check out the code above for an example.
makeAutoObservable
函数比使用 makeObservable
更紧凑且更易于维护,因为不必明确提及新成员。但是,makeAutoObservable
不能用于具有 super 或 subclassed 的类。
¥The makeAutoObservable
function can be more compact and easier to maintain than using makeObservable
, since new members don't have to be mentioned explicitly.
However, makeAutoObservable
cannot be used on classes that have super or are subclassed.
推断规则:
¥Inference rules:
所有拥有的属性都变成
observable
。¥All own properties become
observable
.所有
getters
都变成computed
。¥All
getters
becomecomputed
.所有
setters
都变成action
。¥All
setters
becomeaction
.所有功能都变成
autoAction
。¥All functions become
autoAction
.所有生成器功能都变为
flow
。(请注意,在某些转译器配置中无法检测到生成器函数,如果流程未按预期工作,请确保显式指定flow
。)¥All generator functions become
flow
. (Note that generator functions are not detectable in some transpiler configurations, if flow doesn't work as expected, make sure to specifyflow
explicitly.)overrides
参数中标有false
的成员将不会被注释。例如,将其用于只读字段,例如标识符。¥Members marked with
false
in theoverrides
argument will not be annotated. For example, using it for read only fields such as identifiers.
observable
用法:
¥Usage:
observable(source, overrides?, options?)
@observable accessor
(字段装饰器)¥
@observable accessor
(field decorator)
observable
注释也可以作为函数调用,以使整个对象立即可观察。source
对象将被克隆,所有成员都将变得可观察,类似于 makeAutoObservable
的做法。同样,可以提供 overrides
图来指定特定成员的注释。查看上面的代码块作为示例。
¥The observable
annotation can also be called as a function to make an entire object observable at once.
The source
object will be cloned and all members will be made observable, similar to how it would be done by makeAutoObservable
.
Likewise, an overrides
map can be provided to specify the annotations of specific members.
Check out the above code block for an example.
observable
返回的对象将是一个代理,这意味着稍后添加到该对象的属性也将被拾取并变得可观察(除非 代理使用 被禁用)。
¥The object returned by observable
will be a Proxy, which means that properties that are added later to the object will be picked up and made observable as well (except when proxy usage is disabled).
observable
方法也可以使用 arrays、映射 和 集合 等集合类型来调用。它们也将被克隆并转换成可观察的对应物。
¥The observable
method can also be called with collections types like arrays, Maps and Sets. Those will be cloned as well and converted into their observable counterparts.
Example: observable array
以下示例创建一个可观察对象并使用 autorun
观察它。使用 Map 和 Set 集合的工作方式类似。
¥The following example creates an observable and observes it using autorun
.
Working with Map and Set collections works similarly.
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'
可观察数组还有一些额外的实用函数:
¥Observable arrays have some additional nifty utility functions:
clear()
从数组中删除所有当前条目。¥
clear()
removes all current entries from the array.replace(newItems)
将数组中的所有现有条目替换为新条目。¥
replace(newItems)
replaces all existing entries in the array with new ones.remove(value)
按值从数组中删除单个项目。如果找到并删除该项目,则返回true
。¥
remove(value)
removes a single item by value from the array. Returnstrue
if the item was found and removed.
Note: primitives and class instances are never converted to observables
原始值不能被 MobX 观察到,因为它们在 JavaScript 中是不可变的(但它们可以是 boxed)。尽管这种机制通常在库之外没有用处。
¥Primitive values cannot be made observable by MobX since they are immutable in JavaScript (but they can be boxed). Although there is typically no use for this mechanism outside libraries.
类实例永远不会通过将它们传递给 observable
或将它们分配给 observable
属性而自动变得可观察。使类成员可观察被认为是类构造函数的责任。
¥Class instances will never be made observable automatically by passing them to observable
or assigning them to an observable
property.
Making class members observable is considered the responsibility of the class constructor.
{🚀} Tip: observable (proxied) versus makeObservable (unproxied)
make(Auto)Observable
和 observable
之间的主要区别在于,第一个修改作为第一个参数传入的对象,而 observable
创建一个可观察的克隆。
¥The primary difference between make(Auto)Observable
and observable
is that the first one modifies the object you are passing in as first argument, while observable
creates a clone that is made observable.
第二个区别是 observable
创建了 Proxy
对象,以便在你将该对象用作动态查找映射时能够捕获将来添加的属性。如果你想要使其可观察的对象具有常规结构,其中所有成员都是预先已知的,我们建议使用 makeObservable
,因为非代理对象速度更快一些,并且更容易在调试器中检查,而 console.log
。
¥The second difference is that observable
creates a Proxy
object, to be able to trap future property additions in case you use the object as a dynamic lookup map.
If the object you want to make observable has a regular structure where all members are known up-front, we recommend to use makeObservable
as non proxied objects are a little faster, and they are easier to inspect in the debugger and console.log
.
因此,make(Auto)Observable
是推荐在工厂函数中使用的 API。请注意,可以将 { proxy: false }
作为选项传递给 observable
以获取非代理克隆。
¥Because of that, make(Auto)Observable
is the recommended API to use in factory functions.
Note that it is possible to pass { proxy: false }
as an option to observable
to get a non proxied clone.
可用注释
¥Available annotations
注解 | 描述 |
---|---|
observable observable.deep | 定义存储状态的可跟踪字段。如果可能,分配给 observable 的任何值都会根据其类型自动转换为(深)observable 、autoAction 或 flow 。仅 plain object 、array 、Map 、Set 、function 、generator function 可以转换。类实例和其他实例保持不变。 |
observable.ref | 与 observable 类似,但仅跟踪重新分配。分配的值将被完全忽略,并且不会自动转换为 observable /autoAction /flow 。例如,如果你打算将不可变数据存储在可观察字段中,请使用此选项。 |
observable.shallow | 与 observable.ref 类似,但用于集合。分配的任何集合都将变得可观察,但集合本身的内容不会变得可观察。 |
observable.struct | 与 observable 类似,只不过结构上等于当前值的任何指定值都将被忽略。 |
action | 将方法标记为将修改状态的操作。查看 actions 了解更多详细信息。不可写。 |
action.bound | 与操作类似,但也会将操作绑定到实例,以便始终设置 this 。不可写。 |
computed | 可用于 getter 以将其声明为可缓存的派生值。查看 computeds 了解更多详细信息。 |
computed.struct | 与 computed 类似,只不过如果重新计算后的结果在结构上与之前的结果相同,则不会通知任何观察者。 |
true | 推断最佳注释。查看 makeAutoObservable 了解更多详细信息。 |
false | 明确不要注释此属性。 |
flow | 创建 flow 来管理异步进程。查看 flow 了解更多详细信息。请注意,TypeScript 中推断的返回类型可能会关闭。不可写。 |
flow.bound | 与流类似,但也会将流绑定到实例,以便始终设置 this 。不可写。 |
override | 适用于继承的 action 、flow 、computed 、action.bound 被子类覆盖。 |
autoAction | 不应显式使用,但由 makeAutoObservable 在幕后使用,根据其调用上下文来标记可充当操作或派生的方法。将在运行时确定该函数是派生函数还是动作函数。 |
局限性
¥Limitations
make(Auto)Observable
仅支持已定义的属性。确保你的 编译器配置正确(或者作为解决方法)在使用make(Auto)Observable
之前已将值分配给所有属性。如果没有正确的配置,声明但未初始化的字段(如class X { y; }
中)将无法正确选取。¥
make(Auto)Observable
only supports properties that are already defined. Make sure your compiler configuration is correct, or as work-around, that a value is assigned to all properties before usingmake(Auto)Observable
. Without correct configuration, fields that are declared but not initialized (like inclass X { y; }
) will not be picked up correctly.makeObservable
只能注释其自己的类定义声明的属性。如果子类或超类引入了可观察字段,则它本身必须为这些属性调用makeObservable
。¥
makeObservable
can only annotate properties declared by its own class definition. If a sub- or superclass introduces observable fields, it will have to callmakeObservable
for those properties itself.options
参数只能提供一次。通过的options
为 "sticky",以后不能更改(例如,在 subclass 中)。¥
options
argument can be provided only once. Passedoptions
are "sticky" and can NOT be changed later (eg. in subclass).每个字段只能注释一次(
override
除外)。subclass 中的字段注释或配置不能更改。¥Every field can be annotated only once (except for
override
). The field annotation or configuration can't change in subclass.非普通对象(类)的所有带注释的字段都是不可配置的。
可以通过configure({ safeDescriptors: false })
{🚀☣️} 禁用.¥All annotated fields of non-plain objects (classes) are non-configurable.
Can be disabled withconfigure({ safeDescriptors: false })
{🚀☣️} .所有不可观察(无状态)字段(
action
、flow
)都是不可写的。
可以通过configure({ safeDescriptors: false })
{🚀☣️} 禁用。¥All non-observable (stateless) fields (
action
,flow
) are non-writable.
Can be disabled withconfigure({ safeDescriptors: false })
{🚀☣️} .只有原型上定义的
action
、computed
、flow
、action.bound
可以被子类覆盖。¥Only
action
,computed
,flow
,action.bound
defined on prototype can be overridden by subclass.默认情况下,TypeScript 不允许你注释私有字段。这可以通过显式传递相关私有字段作为通用参数来克服,如下所示:
makeObservable<MyStore, "privateField" | "privateField2">(this, { privateField: observable, privateField2: observable })
¥By default TypeScript will not allow you to annotate private fields. This can be overcome by explicitly passing the relevant private fields as generic argument, like this:
makeObservable<MyStore, "privateField" | "privateField2">(this, { privateField: observable, privateField2: observable })
调用
make(Auto)Observable
并提供注释必须无条件完成,因为这样可以缓存推断结果。¥Calling
make(Auto)Observable
and providing annotations must be done unconditionally, as this makes it possible to cache the inference results.不支持在调用
make(Auto)Observable
后修改原型。¥Modifying prototypes after
make(Auto)Observable
has been called is not supported.不支持 EcmaScript 私有字段 (
#field
)。使用 TypeScript 时,建议使用private
修饰符。¥EcmaScript private fields (
#field
) are not supported. When using TypeScript, it is recommended to use theprivate
modifier instead.不支持在单个继承链中混合注释和装饰器 - 例如。 你不能对超类使用装饰器,对子类使用注释。
¥Mixing annotations and decorators within single inheritance chain is not supported - eg. you can't use decorators for superclass and annotations for subclass.
makeObservable
,extendObservable
不能用于其他内置可观察类型(ObservableMap
、ObservableSet
、ObservableArray
等)¥
makeObservable
,extendObservable
cannot be used on other builtin observable types (ObservableMap
,ObservableSet
,ObservableArray
, etc)makeObservable(Object.create(prototype))
将属性从prototype
复制到创建的对象并使它们成为observable
。这种行为是错误的、意外的,因此已被弃用,并且可能会在未来的版本中发生变化。不要依赖它。¥
makeObservable(Object.create(prototype))
copies properties fromprototype
to created object and makes themobservable
. This behavior is wrong, unexpected and therefore deprecated and will likely change in future versions. Don't rely on it.
选项 {🚀}
¥Options {🚀}
上述 API 采用可选的 options
参数,该参数是支持以下选项的对象:
¥The above APIs take an optional options
argument which is an object that supports the following options:
autoBind: true
默认使用action.bound
/flow.bound
,而不是action
/flow
。不影响显式注释的成员。¥
autoBind: true
usesaction.bound
/flow.bound
by default, rather thanaction
/flow
. Does not affect explicitely annotated members.deep: false
默认使用observable.ref
,而不是observable
。不影响显式注释的成员。¥
deep: false
usesobservable.ref
by default, rather thanobservable
. Does not affect explicitely annotated members.name: <string>
为对象提供一个调试名称,该名称打印在错误消息和反射 API 中。¥
name: <string>
gives the object a debug name that is printed in error messages and reflection APIs.proxy: false
强制observable(thing)
使用非 proxy 实现。如果对象的形状不会随时间变化,这是一个不错的选择,因为非代理对象更容易调试且速度更快。此选项不适用于make(Auto)Observable
,请参阅 避免代理。¥
proxy: false
forcesobservable(thing)
to use non-proxy implementation. This is a good option if the shape of the object will not change over time, as non-proxied objects are easier to debug and faster. This option is not available formake(Auto)Observable
, see avoiding proxies.
Note: options are sticky and can be provided only once
options
argument can be provided only for target
that is NOT observable yet.It is NOT possible to change options once the observable object was initialized.
Options are stored on target and respected by subsequent
makeObservable
/extendObservable
calls.You can't pass different options in subclass.
将可观察量转换回普通 JavaScript 集合
¥Converting observables back to vanilla JavaScript collections
有时有必要将可观察的数据结构转换回其普通的对应结构。例如,当将可观察对象传递给无法跟踪可观察对象的 React 组件时,或者获取不应进一步修改的克隆时。
¥Sometimes it is necessary to convert observable data structures back to their vanilla counterparts. For example when passing observable objects to a React component that can't track observables, or to obtain a clone that should not be further mutated.
要浅层转换集合,通常的 JavaScript 机制可以工作:
¥To convert a collection shallowly, the usual JavaScript mechanisms work:
const plainObject = { ...observableObject }
const plainArray = observableArray.slice()
const plainMap = new Map(observableMap)
要将数据树递归转换为普通对象,可以使用 toJS
实用程序。对于类,建议实现 toJSON()
方法,因为它将被 JSON.stringify
选取。
¥To convert a data tree recursively to plain objects, the toJS
utility can be used.
For classes, it is recommended to implement a toJSON()
method, as it will be picked up by JSON.stringify
.
关于类的简短说明
¥A short note on classes
到目前为止,上面的大多数示例都倾向于类语法。MobX 原则上对此不持任何意见,并且可能有同样多的 MobX 用户使用普通对象。然而,类的一个小好处是它们具有更容易发现的 API,例如 TypeScript。此外,instanceof
检查对于类型推断来说非常强大,并且类实例没有封装在 Proxy
对象中,这让它们在调试器中获得更好的体验。最后,类受益于大量引擎优化,因为它们的形状是可预测的,并且方法在原型上共享。但是繁重的继承模式很容易成为步兵,所以如果你使用类,请保持它们简单。因此,尽管人们稍微倾向于使用类,但如果这种风格更适合你,我们绝对希望鼓励你偏离这种风格。
¥So far most examples above have been leaning towards the class syntax.
MobX is in principle unopinionated about this, and there are probably just as many MobX users that use plain objects.
However, a slight benefit of classes is that they have more easily discoverable APIs, e.g. TypeScript.
Also, instanceof
checks are really powerful for type inference, and class instances aren't wrapped in Proxy
objects, giving them a better experience in debuggers.
Finally, classes benefit from a lot of engine optimizations, since their shape is predictable, and methods are shared on the prototype.
But heavy inheritance patterns can easily become foot-guns, so if you use classes, keep them simple.
So, even though there is a slight preference to use classes, we definitely want to encourage you to deviate from this style if that suits you better.