Skip to main content

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响应式对象 + keyRef需要保持单个属性的响应式引用
toRefs响应式对象普通对象 + Ref需要解构响应式对象
toValueRef/函数/原始值原始值需要统一处理各种值来源

toRef 和 toRefs 都是将响应式对象的属性转成 ref,延续响应式并且保持和原属性的双向同步,但 toRef 针对的是单个属性,而 toRefs 是对整个对象的所有属性进行批量转换。