使用计算值推导信息
用法
computed
(注解)computed(options)
(注解)computed(fn, options?)
@computed
(getter 装饰器)@computed(options)
(getter 装饰器)
计算值可以用来从其他可观察对象中推导信息。它们延迟计算,缓存其输出,并且只有在底层可观察对象之一发生变化时才重新计算。如果它们不被任何东西观察,它们会完全暂停。
从概念上讲,它们与电子表格中的公式非常相似,不可低估。它们有助于减少必须存储的状态数量,并且高度优化。在可能的情况下使用它们。
例子
计算值可以通过使用 computed
注解 JavaScript getter 来创建。使用 makeObservable
将 getter 声明为计算值。如果你希望所有 getter 都自动声明为 computed
,可以使用 makeAutoObservable
、observable
或 extendObservable
。计算 getter 成为不可枚举的。
为了说明计算值的意义,下面的例子依赖于 autorun
,来自 反应 {🚀} 高级部分。
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
。
相比之下,如果 total
没有被注释,autorun
会运行其效果 3 次,因为它将直接依赖于 total
和 amount
。自己试试看.
这是上面例子中创建的依赖关系图。
规则
使用计算值时,有一些最佳实践需要遵循。
- 它们不应该有副作用或更新其他可观察对象。
- 避免创建和返回新的可观察对象。
- 它们不应该依赖于不可观察的值。
提示
提示:如果计算值没有被观察,它们将被暂停
有时,MobX 的新手会感到困惑,也许他们习惯于使用像 Reselect 这样的库,如果你创建了一个计算属性但没有在任何反应中使用它,它不会被记忆,并且看起来比必要时计算得更多。例如,如果我们在上面的例子中扩展了调用 console.log(order.total)
两次,在我们调用 stop()
之后,该值将被重新计算两次。
这使得 MobX 可以自动暂停那些没有积极使用的计算,以避免对那些没有被访问的计算值的无用更新。但如果一个计算属性没有被某个反应使用,那么计算表达式每次请求其值时都会被计算,因此它们的行为就像一个普通的属性。
如果你只对计算属性进行调整,它们可能看起来效率不高,但当应用在使用 observer
、autorun
等等的项目中时,它们就会变得非常高效。
以下代码演示了这个问题。
// 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 })
,它可以在需要时被很好地清理掉。请注意,这两种方法都存在创建内存泄漏的风险。更改这里的默认行为是一种反模式。
MobX 也可以使用 computedRequiresReaction
选项进行配置,以便在计算值在非反应式上下文中访问时报告错误。
提示:计算值可以有 setter
也可以为计算值定义 setter。请注意,这些 setter 不能用于直接改变计算属性的值,但它们可以用作推导的“反向”。setter 自动标记为动作。例如。
class Dimension {
length = 2
constructor() {
makeAutoObservable(this)
}
get squared() {
return this.length * this.length
}
set squared(value) {
this.length = Math.sqrt(value)
}
}
{🚀} 提示:computed.struct
用于结构化比较输出
如果一个计算值的输出与之前的计算在结构上等效,不需要通知观察者,可以使用 computed.struct
。它将在通知观察者之前首先进行结构化比较,而不是引用相等性检查。例如
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
}
}
}
默认情况下,计算值的输出是通过引用进行比较的。由于上面的例子中的 topRight
将始终产生一个新的结果对象,它永远不会被认为与之前的输出相等。除非使用了 computed.struct
。
然而,在上面的例子中,我们实际上不需要 computed.struct
!计算值通常只在支持值发生变化时重新计算。这就是为什么 topRight
只有在 width
或 height
发生变化时才会做出反应。因为如果这两个值中的任何一个发生变化,我们无论如何都会得到一个不同的 topRight
坐标。computed.struct
永远不会命中缓存,而且是一种浪费,所以我们不需要它。
在实践中,computed.struct
的用处并不像听起来那样大。只有在底层可观察对象的更改仍然会导致相同输出时才使用它。例如,如果我们首先对坐标进行四舍五入,那么即使底层值不同,四舍五入后的坐标也可能与之前四舍五入后的坐标相等。
查看 equals
选项以获取关于确定输出是否已更改的进一步自定义。
{🚀} 提示:带参数的计算值
尽管 getter 不接受参数,但几种处理需要参数的派生值的方法在 这里 进行了讨论。
{🚀} 提示:使用 computed(expression)
创建独立的计算值
computed
也可以像 observable.box
创建一个独立的计算值那样直接作为函数调用。使用 .get()
对返回的对象进行操作,以获取计算的当前值。这种形式的 computed
并不经常使用,但在某些情况下,你需要传递一个“盒装”的计算值,它可能被证明是有用的,其中一个例子在 这里 进行了讨论。
选项 {🚀}
computed
通常按照你想要的方式工作,但可以通过传入一个 options
参数来定制它的行为。
name
这个字符串用作 Spy 事件监听器 和 MobX 开发者工具 中的调试名称。
equals
默认情况下设置为comparer.default
。 它充当比较函数,用于比较前一个值和下一个值。 如果此函数认为这两个值相等,则观察者将不会重新评估。
这在处理结构化数据和来自其他库的类型时很有用。 例如,计算后的 moment 实例可以使用(a, b) => a.isSame(b)
。 comparer.structural
和 comparer.shallow
非常方便,如果你想使用结构化/浅层比较来确定新值是否与前一个值不同,并因此通知其观察者。
查看上面的 computed.struct
部分。
内置比较器
MobX 提供了四种内置的 comparer
方法,这些方法应该可以满足 computed
的 equals
选项的大多数需求。
comparer.identity
使用恒等(===
)运算符来确定两个值是否相同。comparer.default
与comparer.identity
相同,但同时也认为NaN
等于NaN
。comparer.structural
执行深度结构化比较来确定两个值是否相同。comparer.shallow
执行浅层结构化比较来确定两个值是否相同。
您可以从 mobx
中导入 comparer
来访问这些方法。 它们也可以用于 reaction
。
requiresReaction
建议将此设置为非常昂贵的计算值上的 true
。 如果你试图在非反应式上下文中读取它的值,在这种情况下它可能没有被缓存,它会导致计算抛出错误,而不是执行昂贵的重新评估。
keepAlive
这避免了在计算值没有被任何东西观察到时(见上面的解释)将其挂起。 可能导致内存泄漏,类似于针对 reactions 的讨论。