近日,Vue 改进了响应式 API 中 getter 的用法,主要包括:
- 一个用于将不同来源(value / ref / getter)规范化为值的 API(通过引入
toValue()
) - 一个用于将不同来源(value / ref / getter)规范化为引用的 API(通过增强
toRef()
) - 引入
MaybeRef<T>
和MaybeRefOrGetter<T>
类型
通常需要将状态传递到组合式函数中并保持响应性。在大多数情况下,这意味着要将响应源转换为 ref:
import { toRef } from 'vue' const props = defineProps(/* ... */) useFeature(toRef(props, 'foo'))
目前,toRef
仅用于从对象中“提取”单个属性。这就有点不灵活,例如,如果想将嵌套属性转换为 ref:
typescript
复制代码
useFeature(toRef(props.foo, 'bar'))
上面的代码有两个问题:
- 调用
toRef
时props.foo
可能不存在 - 如果
props.foo
被交换到不同的对象,这将无法处理这种情况。
为了解决这个问题,可以使用 computed
:
typescript
复制代码
useFeature(computed(() => props.foo?.bar))
但是,在这里使用 computed
并不是最佳选择。在内部,computed
创建一个单独的 effect
来缓存计算值。当 getter
只访问属性而不执行任何昂贵的计算时,这实际上是很大的开销。
将非引用响应状态传递到组合式函数的成本最低的方法就是用 getter
包装它(或“thunking”——即延迟实际值的访问,直到调用 getter
):
typescript
复制代码
useFeature(() => props.foo?.bar)
VueUse 已经广泛支持这种模式,这也有点类似于在 Solid 中看到的函数式 signals(信号)。
此外,这种模式在使用响应式 props 解构时会很常用:
const { foo } = defineProps<{ foo: string }>() useFeature(() => foo)
引入 toValue()
在组合式函数中,其参数可以接受值或引用。这可以表示为:
typescript
复制代码
typeMaybeRef<T> = T | Ref<T>
为了也支持 getters
,接受的类型将是:
typescript
复制代码
typeMaybeRefOrGetter<T> = MaybeRef<T> | (() => T)
目前提供的unref
将 MaybeRef<T>
规范化为 T
。但是,不能让 unref
也解包 getter
,因为这将是一个破坏性的变化。可以在函数值上调用 unref
并期望返回该函数。这种情况比较少见,但仍然是无法破解的可能情况。
因此,引入一个新方法,toValue()
:
typescript
复制代码
exportfunction toValue<T>(source: MaybeRefOrGetter<T>): T {
returnisFunction(source) ? source() : unref(source)}
这就相当于 VueUse 的 [resolveUnref()](https://vueuse.org/shared/resolveUnref/)
。 这里将其命名为 toValue()
是因为它与 toRef()
相反:两者代表两个不同的规范化方向:
typescript
复制代码
ref <- toRef() - ref/value/getter - toValue() -> value
增强 toRef()
当然也可能存在需要 ref
的情况——不能传递 getter
。 对于这种情况,仍然可以使用 computed
。但如前所述,computed
对于只访问属性的简单 getter
来说是一种矫枉过正。
可以向 toRef()
添加新的重载,以便它现在可以接受 getters
:
const ref = toRef(() => 123) ref.value // 123
以这种方式创建的 ref
是只读的,只是在每次访问 .value
时调用 getter
。
toRef()
现在应该被视为“将 value / ref / getter 规范化为 refs” 的 API:
// value -> Ref toRef(1) // Ref<number> // Ref -> Ref toRef(ref(1)) // Ref<number> // getter -> Ref toRef(() => 1) // Ref<number>
这就相当于 VueUse 的 [resolveRef()](https://vueuse.org/shared/resolveRef/)
。
现在仍然支持旧的 toRef(object, 'key')
用法,但应该首选更灵活的 getter
语法:
typescript
复制代码
toRef(() =>object.key)
向后移植到 v2.7?
将这些功能添加在 v3.3 和 v2.7 之间造成了差异——虽然理论上不再回向 v2.7 添加新的功能,但这些可能值得向后移植以确保 vue-demi
和依赖于 vue-demi
的 VueUse 的行为一致性。
如果向后移植到 2.7,VueUse 也可以用 toRef
和 toValue
来替换 resolveRef
和 resolveUnref
。