帶參數的計算屬性 {🚀}
computed
標註只能用於 getter,而 getter 不能接受參數。那麼需要參數的計算該怎麼辦?以下面的例子來說,一個 React 元件渲染一個特定的 Item
,並且應用程式支援多選。
我們如何實現像 store.isSelected(item.id)
這樣的衍生?
import * as React from 'react'
import { observer } from 'mobx-react-lite'
const Item = observer(({ item, store }) => (
<div className={store.isSelected(item.id) ? "selected" : ""}>
{item.title}
</div>
))
我們可以用四種方法來處理這個問題。您可以在 這個 CodeSandbox 中嘗試以下解決方案。
computed
1. 衍生計算不一定要使用 一個函式不需要標記為 computed
就能被 MobX 追蹤。上面的例子在沒有任何修改的情況下就可以正常工作。重要的是要意識到,計算值只是快取點。如果衍生計算是純粹的(而且它們應該如此),那麼使用沒有 computed
的 getter 或函式不會改變行為,只是效率略低。
上面的例子即使 isSelected
不是 computed
也能正常工作。observer
元件會偵測並訂閱 isSelected
讀取的任何可觀察物件,因為該函式作為被追蹤的渲染過程的一部分執行。
需要注意的是,在這種情況下,所有 Item
元件都會響應未來的選擇變更,因為它們都直接訂閱了擷取選擇的可觀察物件。這是一個最壞的情況。一般來說,擁有未標記的衍生資訊函式是完全可以接受的,這是一個很好的預設策略,除非數字證明應該採取其他措施。
2. 閉包參數
與原始方法相比,這是一個更有效率的實現。
import * as React from 'react'
import { computed } from 'mobx'
import { observer } from 'mobx-react-lite'
const Item = observer(({ item, store }) => {
const isSelected = computed(() => store.isSelected(item.id)).get()
return (
<div className={isSelected ? "selected" : ""}>
{item.title}
</div>
)
})
我們在一個反應過程中建立一個新的計算值。這可以正常工作,並且確實引入了額外的快取點,避免所有組件都必須直接響應每個選擇變化。這種方法的優點是組件本身只會在 isSelected
狀態切換時重新渲染,在這種情況下,我們確實需要重新渲染來交換 className
。
我們在下一次渲染中建立一個新的 computed
是可以的,這個新的計算值將成為快取點,前一個會被妥善清除。這是一個很棒的進階優化技巧。
3. 移動狀態
在這種特定情況下,選擇也可以作為 isSelected
可觀察物件儲存在 Item
上。儲存區中的選擇可以表示為 computed
而不是可觀察物件:get selection() { return this.items.filter(item => item.isSelected) }
,我們不再需要 isSelected
方法了。
4. 使用 computedFn {🚀}
最後,可以使用 mobx-utils
中的 computedFn
來定義 todoStore.selected
,自動記憶 isSelected
。它會建立一個函式,針對每種輸入參數組合記憶輸出。
我們建議不要過快地使用這個方法。對於記憶體化來說,在推斷記憶體消耗之前,您需要考慮函式將被呼叫多少次不同的參數。然而,如果任何反應都沒有觀察到它們的結果,它會自動清除條目,因此在正常情況下不會洩漏記憶體。
再次,請查看 連結的 CodeSandbox 來嘗試這個方法。