使用 Computed 值推導資訊
用法
computed
(註解)computed(options)
(註解)computed(fn, options?)
@computed
(getter 裝飾器)@computed(options)
(getter 裝飾器)
Computed 值可以用來從其他可觀察物件推導資訊。它們會延遲計算,快取其輸出,並且只有在其中一個基礎可觀察物件發生變化時才會重新計算。如果它們沒有被任何東西觀察到,它們就會完全暫停。
從概念上講,它們與試算表中的公式非常相似,並且不容小覷。它們有助於減少您必須儲存的狀態量,並且經過高度優化。盡可能使用它們。
範例
可以通過使用 computed
註解 JavaScript getter 來創建 Computed 值。使用 makeObservable
將 getter 聲明為 computed。如果您希望所有 getter 都自動聲明為 computed
,則可以使用 makeAutoObservable
、observable
或 extendObservable
。 Computed getter 將變為不可列舉。
為了說明 Computed 值的重點,以下範例依賴於進階章節 反應 {🚀} 中的 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
。 自己試試看。
這是將為上述範例創建的依賴關係圖。
規則
使用 Computed 值時,有一些最佳實務可以遵循
- 它們不應該有副作用或更新其他可觀察物件。
- 避免創建和返回新的可觀察物件。
- 它們不應該依賴於不可觀察的值。
技巧
**技巧:**如果 Computed 值*沒有*被觀察到,它們將會被暫停
對於 MobX 的新手來說,有時會感到困惑,也許習慣了像 Reselect 這樣的函式庫,如果您創建了一個 Computed 屬性,但在任何反應中都沒有使用它,它就不會被記憶體化,並且似乎比必要的更頻繁地重新計算。例如,如果我們擴展上面的例子,在調用 stop()
之後,調用兩次 console.log(order.total)
,該值將會被重新計算兩次。
這允許 MobX 自動暫停未 actively 使用的計算,以避免對未被訪問的 Computed 值進行不必要的更新。但是,如果 Computed 屬性*沒有*被某些反應使用,則每次請求其值時都會計算 Computed 運算式,因此它們的行為就像普通屬性一樣。
如果您只是隨意使用 Computed 屬性,它們可能看起來效率不高,但是當應用在使用 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
選項進行配置,以便在反應上下文之外訪問 computed 時報告錯誤。
**技巧:**Computed 值可以有 setter
也可以為 Computed 值定義 setter。請注意,這些 setter 不能用於直接更改 Computed 屬性的值,但它們可以用作推導的「反向」。 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 值的輸出與先前的計算結構上等效,則不需要通知觀察者,可以使用 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
}
}
}
預設情況下,computed
的輸出是通過參考進行比較的。由於上述範例中的 topRight
將始終產生一個新的結果物件,因此它永遠不會被視為與先前的輸出相等。除非使用 computed.struct
。
但是,在上面的例子中,*我們實際上不需要 computed.struct
*! Computed 值通常只在後備值更改時重新計算。這就是為什麼 topRight
只會對 width
或 height
的更改做出反應。因為如果其中任何一個更改,我們無論如何都會得到不同的 topRight
坐標。 computed.struct
永遠不會有快取命中,而且是浪費精力,所以我們不需要它。
在實踐中,computed.struct
的用處不如聽起來那麼大。僅當基礎可觀察物件的更改仍然可能導致相同輸出時才使用它。例如,如果我們先對坐標進行四捨五入,則即使基礎值不同,四捨五入的坐標也可能等於先前四捨五入的坐標。
查看 equals
選項,以進一步自定義確定輸出是否已更改。
{🚀} **技巧:**帶參數的 Computed 值
雖然 getter 不帶參數,但 此處 討論了幾種處理需要參數的派生值的策略。
{🚀} **技巧:**使用 computed(expression)
創建獨立的 Computed 值
也可以像 observable.box
創建獨立的 Computed 值一樣,直接將 computed
作為函數調用。在返回的物件上使用 .get()
來獲取計算的當前值。這種形式的 computed
並不常用,但在某些情況下,您需要傳遞一個「盒裝」的 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 中討論的那些。