toRef〡toRefs〡toValue
toRef 和 toRefs 不创造响应式,而是延续响应式。
一、toRef:保持响应式连接
将响应式对象的某个属性转换为 ref,该 ref 与其源属性保持同步,改变源属性的值将更新 ref 的值,反之亦然。
- 源对象必须已经是响应式的
- 访问不存在的属性会创建非响应式 ref
- 与普通 ref 的区别:同步源属性变化
// 规范化签名 (3.3+)
function toRef<T>(
value: T
): T extends () => infer R
? Readonly<Ref<R>>
: T extends Ref
? T
: Ref<UnwrapRef<T>>
// 对象属性签名
function toRef<T extends object, K extends keyof T>(
object: T,
key: K,
defaultValue?: T[K]
): ToRef<T[K]>
type ToRef<T> = T extends Ref ? T : Ref<T>
1.1 基本用法
const state = reactive({
a: 1,
b: 2
})
// 双向 ref,会与源属性同步
const aRef = toRef(state, 'a')
// 更改该 ref 会更新源属性
aRef.value++
console.log(state.a) // 2
// 更改源属性也会更新该 ref
state.a++
console.log(aRef.value) // 3
注意,上面的 const aRef = toRef(state, 'a')
不同于:
const aRef = ref(state.a)
ref 不会和 state.a
保持同步,因为 ref()
接收到的是一个纯数值。
1.2 进阶用法
在组合式 API 中,直接解构 props 会丢失响应性,使用 toRef
可以保持响应性。
const props = defineProps({ userId: Number });
// 危险解构 ❌
const { userId } = props;
// 安全守卫 ✅
const userId = toRef(props, "userId");
二、toRefs:解构响应式对象
将响应式对象转为普通对象,这个普通对象的每个属性都是指向源对象对应属性的 ref。每个单独的 ref 都是使用 toRef()
创建的。
function toRefs<T extends object>(
object: T
): {
[K in keyof T]: ToRef<T[K]>
}
type ToRef = T extends Ref ? T : Ref<T>
2.1 基本用法
const state = reactive({
a: 1,
b: 2
})
const stateAsRefs = toRefs(state)
/*
stateAsRefs 的类型:{
a: Ref<number>,
b: Ref<number>
}
*/
// 这个 ref 和源属性已经“链接上了”
state.a++
console.log(stateAsRefs.a.value) // 2
stateAsRefs.a.value++
console.log(state.a) // 3
toRefs
在调用时只会为源对象上可以枚举的属性创建 ref。如果要为可能还不存在的属性创建 ref,请改用 toRef
。
与扩展运算符的对比:
// ❌ 错误:失去响应性
const { count } = reactive({ count: 0 });
// ✅ 正确:保持响应性
const { count } = toRefs(reactive({ count: 0 }));
三、toValue:万能值提取器
统一处理 ref、getter 函数或普通值,返回原始值。
function toValue<T>(source: T | Ref<T> | (() => T)): T
3.1 基本用法
输入类型 | 处理方式 |
---|---|
Ref | 返回 .value |
函数 | 执行并返回结果 |
普通值 | 直接返回值 |
处理多种值类型:
toValue(1) // --> 1
toValue(ref(1)) // --> 1
toValue(() => 1) // --> 1
在组合式函数中的应用:
import type { MaybeRefOrGetter } from 'vue'
function useFeature(id: MaybeRefOrGetter<number>) {
watch(() => toValue(id), id => {
// 处理 id 变更
})
}
// 这个组合式函数支持以下的任意形式:
useFeature(1)
useFeature(ref(1))
useFeature(() => 1)
四、三者的对比与选择
函数 | 输入类型 | 输出类型 | 主要应用场景 |
---|---|---|---|
toRef | 响应式对象 + key | Ref | 需要保持单个属性的响应式引用 |
toRefs | 响应式对象 | 普通对象 + Ref | 需要解构响应式对象 |
toValue | Ref/函数/原始值 | 原始值 | 需要统一处理各种值来源 |
toRef 和 toRefs 都是将响应式对象的属性转成 ref,延续响应式并且保持和原属性的双向同步,但 toRef 针对的是单个属性,而 toRefs 是对整个对象的所有属性进行批量转换。