通过计算得出信息
¥Deriving information with computeds
用法:
¥Usage:
computed
(注解)¥
computed
(annotation)computed(options)
(注解)¥
computed(options)
(annotation)computed(fn, options?)
@computed
(获取器装饰器)¥
@computed
(getter decorator)@computed(options)
(获取器装饰器)¥
@computed(options)
(getter decorator)
计算值可用于从其他可观测值中获取信息。它们惰性地评估,缓存其输出,并且仅在底层可观察值之一发生变化时才重新计算。如果没有任何东西观察到它们,它们就会完全暂停。
¥Computed values can be used to derive information from other observables. They evaluate lazily, caching their output and only recomputing if one of the underlying observables has changed. If they are not observed by anything, they suspend entirely.
从概念上讲,它们与电子表格中的公式非常相似,并且不可低估。它们有助于减少必须存储的状态量,并且经过高度优化。尽可能使用它们。
¥Conceptually, they are very similar to formulas in spreadsheets, and can't be underestimated. They help in reducing the amount of state you have to store and are highly optimized. Use them wherever possible.
示例
¥Example
可以通过用 computed
注释 JavaScript getters 来创建计算值。使用 makeObservable
将 getter 声明为计算值。如果你希望所有 getter 自动声明为 computed
,则可以使用 makeAutoObservable
、observable
或 extendObservable
。计算的 getter 变得不可枚举。
¥Computed values can be created by annotating JavaScript getters with computed
.
Use makeObservable
to declare a getter as computed. If you instead want all getters to be automatically declared as computed
, you can use either makeAutoObservable
, observable
or extendObservable
. Computed getters become non-enumerable.
为了帮助说明计算值的要点,下面的示例依赖于 反应 {🚀} 高级部分中的 autorun
。
¥To help illustrate the point of computed values, the example below relies on autorun
from the Reactions {🚀} advanced section.
import { makeObservable, observable, computed, autorun } from "mobx"
class OrderLine {
price = 0
amount = 1
constructor(price) {
makeObservable(this, {
price: observable,
amount: observable,
total: computed
})
this.price = price
}
get total() {
console.log("Computing...")
return this.price * this.amount
}
}
const order = new OrderLine(0)
const stop = autorun(() => {
console.log("Total: " + order.total)
})
// Computing...
// Total: 0
console.log(order.total)
// (No recomputing!)
// 0
order.amount = 5
// Computing...
// (No autorun)
order.price = 2
// Computing...
// Total: 10
stop()
order.price = 3
// Neither the computation nor autorun will be recomputed.
上面的示例很好地演示了 computed
值的好处,它充当缓存点。即使我们更改了 amount
,这将触发 total
重新计算,但它不会触发 autorun
,因为 total
会检测到其输出没有受到影响,因此无需更新 autorun
。
¥The above example nicely demonstrates the benefits of a computed
value, it acts as a caching point.
Even though we change the amount
, and this will trigger the total
to recompute,
it won't trigger the autorun
, as total
will detect its output hasn't been affected, so there is no need to update the autorun
.
相比之下,如果不注释 total
,autorun
将运行其效果 3 次,因为它直接依赖于 total
和 amount
。自己尝试一下。
¥In comparison, if total
would not be annotated, the autorun
would run its effect 3 times,
as it would directly depend on total
and amount
. Try it out yourself.
这是将为上面的示例创建的依赖图。
¥This is the dependency graph that would be created for the above example.
规则
¥Rules
使用计算值时,需要遵循一些最佳实践:
¥When using computed values there are a couple of best practices to follow:
它们不应该有副作用或更新其他可观察到的结果。
¥They should not have side effects or update other observables.
避免创建和返回新的可观察量。
¥Avoid creating and returning new observables.
它们不应依赖于不可观察的值。
¥They should not depend on non-observable values.
提示
¥Tips
Tip: computed values will be suspended if they are not observed
它有时会让刚接触 MobX 的人感到困惑,也许习惯了像 重新选择 这样的库,如果你创建一个计算属性但不在反应中的任何地方使用它,它就不会被记忆,并且似乎会比必要的情况更频繁地重新计算。例如,如果我们将上面的示例扩展为调用 console.log(order.total)
两次,那么在调用 stop()
后,该值将被重新计算两次。
¥It sometimes confuses people new to MobX, perhaps used to a library like Reselect, that if you create a computed property but don't use it anywhere in a reaction, it is not memoized and appears to be recomputed more often than necessary.
For example, if we extended the above example with calling console.log(order.total)
twice, after we called stop()
, the value would be recomputed twice.
这允许 MobX 自动暂停未主动使用的计算,以避免对未访问的计算值进行不必要的更新。但是,如果某些反应未使用计算属性,则每次请求计算表达式的值时都会对计算表达式进行求值,因此它们的行为就像普通属性一样。
¥This allows MobX to automatically suspend computations that are not actively in use to avoid unnecessary updates to computed values that are not being accessed. But if a computed property is not in use by some reaction, then computed expressions are evaluated each time their value is requested, so they behave just like a normal property.
如果你只摆弄计算属性可能看起来效率不高,但是当应用于使用 observer
、autorun
等的项目时,它们会变得非常高效。
¥If you only fiddle around computed properties might not seem efficient, but when applied in a project that uses observer
, autorun
, etc., they become very efficient.
以下代码演示了该问题:
¥The following code demonstrates the issue:
// OrderLine has a computed property `total`.
const line = new OrderLine(2.0)
// If you access `line.total` outside of a reaction, it is recomputed every time.
setInterval(() => {
console.log(line.total)
}, 60)
可以通过使用 keepAlive
选项 (自己尝试一下) 设置注释或创建无操作 autorun(() => { someObject.someComputed })
来覆盖它,如果需要,可以在以后很好地清理它。请注意,这两种解决方案都有造成内存泄漏的风险。在这里更改默认行为是一种反模式。
¥It can be overridden by setting the annotation with the keepAlive
option (try it out yourself) or by creating a no-op autorun(() => { someObject.someComputed })
, which can be nicely cleaned up later if needed.
Note that both solutions have the risk of creating memory leaks. Changing the default behavior here is an anti-pattern.
MobX 还可以使用 computedRequiresReaction
选项进行配置,以便在响应式上下文之外访问计算值时报告错误。
¥MobX can also be configured with the computedRequiresReaction
option, to report an error when computeds are accessed outside of a reactive context.
Tip: computed values can have setters
也可以为计算值定义 setter。请注意,这些 setter 不能用于直接更改计算属性的值,但它们可以用作派生的 "inverse"。Setter 会自动标记为操作。例如:
¥It is possible to define a setter for computed values as well. Note that these setters cannot be used to alter the value of the computed property directly, but they can be used as an "inverse" of the derivation. Setters are automatically marked as actions. For example:
class Dimension {
length = 2
constructor() {
makeAutoObservable(this)
}
get squared() {
return this.length * this.length
}
set squared(value) {
this.length = Math.sqrt(value)
}
}
{🚀} Tip: computed.struct
for comparing output structurally
如果结构上与先前计算等效的计算值的输出不需要通知观察者,则可以使用 computed.struct
。在通知观察者之前,它将首先进行结构比较,而不是引用相等性检查。例如:
¥If the output of a computed value that is structurally equivalent to the previous computation doesn't need to notify observers, computed.struct
can be used. It will make a structural comparison first, rather than a reference equality check, before notifying observers. For example:
class Box {
width = 0
height = 0
constructor() {
makeObservable(this, {
width: observable,
height: observable,
topRight: computed.struct
})
}
get topRight() {
return {
x: this.width,
y: this.height
}
}
}
默认情况下,computed
的输出通过引用进行比较。由于上例中的 topRight
始终会生成一个新的结果对象,因此它永远不会被视为等于先前的输出。除非使用 computed.struct
。
¥By default, the output of a computed
is compared by reference. Since topRight
in the above example will always produce a new result object, it is never going to be considered equal to a previous output. Unless computed.struct
is used.
然而,在上面的例子中我们实际上并不需要 computed.struct
!计算值通常仅在支持值发生变化时才重新评估。这就是为什么 topRight
只会对 width
或 height
的变化做出反应。因为如果其中任何一个发生变化,我们无论如何都会得到不同的 topRight
坐标。computed.struct
永远不会有缓存命中并且会浪费精力,所以我们不需要它。
¥However, in the above example we actually don't need computed.struct
!
Computed values normally only re-evaluate if the backing values change.
That's why topRight
will only react to changes in width
or height
.
Since if any of those change, we would get a different topRight
coordinate anyway. computed.struct
would never have a cache hit and be a waste of effort, so we don't need it.
实际上,computed.struct
并没有听起来那么有用。仅当底层可观察量的变化仍可导致相同的输出时才使用它。例如,如果我们首先对坐标进行舍入,则舍入坐标可能等于之前舍入的坐标,即使基础值并非如此。
¥In practice, computed.struct
is less useful than it sounds. Only use it if changes in the underlying observables can still lead to the same output. For example, if we were rounding the coordinates first, the rounded coordinates might be equal to the previously rounded coordinates even though the underlying values aren't.
查看 equals
选项以进一步自定义以确定输出是否已更改。
¥Check out the equals
option for further customizations on determining whether the output has changed.
{🚀} Tip: computed values with arguments
虽然 getter 不接受参数,但 此处 讨论了使用需要参数的派生值的几种策略。
¥Although getters don't take arguments, several strategies to work with derived values that need arguments are discussed here.
{🚀} Tip: create standalone computed values with computed(expression)
computed
也可以直接作为函数调用,就像 observable.box
创建独立的计算值一样。在返回的对象上使用 .get()
来获取计算的当前值。这种形式的 computed
并不经常使用,但在某些情况下,你需要在其周围传递 "boxed" 计算值,这可能会证明自己很有用,其中讨论了 此处 中的一种情况。
¥computed
can also be invoked directly as a function, just like observable.box
creates a standalone computed value.
Use .get()
on the returned object to get the current value of the computation.
This form of computed
is not used very often, but in some cases where you need to pass a "boxed" computed value around it might prove itself useful, one such case is discussed here.
选项 {🚀}
¥Options {🚀}
computed
通常按照你希望的开箱即用方式运行,但可以通过传入 options
参数来自定义其行为。
¥computed
usually behaves the way you want it to out of the box, but it's possible to customize its behavior by passing in an options
argument.
name
该字符串在 间谍事件监听器 和 MobX 开发者工具 中用作调试名称。
¥This string is used as a debug name in the Spy event listeners and MobX developer tools.
equals
默认设置为 comparer.default
。它充当比较函数,用于将前一个值与下一个值进行比较。如果该函数认为值相等,则不会重新评估观察者。
¥Set to comparer.default
by default. It acts as a comparison function for comparing the previous value with the next value. If this function considers the values to be equal, then the observers will not be re-evaluated.
当使用其他库中的结构数据和类型时,这非常有用。例如,计算的 moment 实例可以使用 (a, b) => a.isSame(b)
。如果你想使用结构/浅比较来确定新值是否与以前的值不同,并通知其观察者,则 comparer.structural
和 comparer.shallow
会派上用场。
¥This is useful when working with structural data and types from other libraries. For example, a computed moment instance could use (a, b) => a.isSame(b)
. comparer.structural
and comparer.shallow
come in handy if you want to use structural / shallow comparison to determine whether the new value is different from the previous value, and as a result notify its observers.
查看上面的 computed.struct
部分。
¥Check out the computed.struct
section above.
内置比较器
¥Built-in comparers
MobX 提供了四种内置的 comparer
方法,应该可以满足 computed
的 equals
选项的大部分需求:
¥MobX provides four built-in comparer
methods which should cover most needs of the equals
option of computed
:
comparer.identity
使用恒等 (===
) 运算符来确定两个值是否相同。¥
comparer.identity
uses the identity (===
) operator to determine if two values are the same.comparer.default
与comparer.identity
相同,但也认为NaN
等于NaN
。¥
comparer.default
is the same ascomparer.identity
, but also considersNaN
to be equal toNaN
.comparer.structural
执行深度结构比较以确定两个值是否相同。¥
comparer.structural
performs deep structural comparison to determine if two values are the same.comparer.shallow
执行浅层结构比较以确定两个值是否相同。¥
comparer.shallow
performs shallow structural comparison to determine if two values are the same.
你可以从 mobx
导入 comparer
来访问这些方法。它们也可用于 reaction
。
¥You can import comparer
from mobx
to access these methods. They can be used for reaction
as well.
requiresReaction
建议在非常昂贵的计算值上将此设置为 true
。如果你尝试在反应式上下文之外读取它的值,在这种情况下它可能不会被缓存,它将导致计算抛出而不是进行昂贵的重新评估。
¥It is recommended to set this one to true
on very expensive computed values. If you try to read its value outside of the reactive context, in which case it might not be cached, it will cause the computed to throw instead of doing an expensive re-evalution.
keepAlive
这可以避免在没有任何东西观察到计算值时暂停计算值(请参阅上面的解释)。可能会造成内存泄漏,类似于 reactions 中讨论的内存泄漏。
¥This avoids suspending computed values when they are not being observed by anything (see the above explanation). Can potentially create memory leaks, similar to the ones discussed for reactions.