带参数的计算属性 {🚀}
computed
注解只能用于没有参数的 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
是可以的,这个新的 computed
现在将成为缓存点,而之前的 computed
将被很好地清理。这是一个很棒且高级的优化技巧。
3. 移动状态
在这个特定情况下,选择也可以存储为 Item
上的 isSelected
可观察对象。然后,存储中的选择可以表示为 computed
而不是可观察对象:get selection() { return this.items.filter(item => item.isSelected) }
,我们不再需要 isSelected
。
4. 使用 computedFn {🚀}
最后,来自 mobx-utils
的 computedFn
可用于 todoStore.selected
的定义中,以自动记忆 isSelected
。它创建一个函数,该函数为每组输入参数记忆输出。
我们建议不要太快使用这种方法。记忆通常需要考虑函数将被调用多少次,以及使用多少个不同的参数,才能推断出内存消耗。但是,如果函数的结果没有被任何反应观察到,它会自动清理条目,因此在正常情况下不会导致内存泄漏。
再次,请查看 链接的 CodeSandbox 以尝试使用此方法。