1. 首页
  2. Vue

Vue 3.3 toRef用法分析

1.抛出问题

分析以下代码,详细解释 toRef 的作用

<script setup>
import { toRef } from 'vue'

const props = defineProps(/* ... */)

// getter 语法——推荐在 3.3+ 版本使用
useSomeFeature(toRef(() => props.foo))
</script>

2.代码分析

这段代码展示了 Vue 3.3+ 中 toRef的新用法,让我详细分析一下:

// 使用 toRef 创建对 props.foo 的响应式引用
useSomeFeature(toRef(() => props.foo))

1.toRef的作用

1.创建响应式引用 toRef在这里的作用是将 props.foo的值转换为一个响应式引用(Ref),当 props.foo发生变化时,这个引用会自动更新。

2.Getter 语法(3.3+ 新特性) 在 Vue 3.3+ 中,toRef支持 getter 函数语法:

  • () => props.foo是一个 getter 函数

  • 每次访问这个 ref 的值时,都会重新执行这个函数获取最新的 props.foo

3.与普通用法的区别

传统用法(3.3 之前):

// 需要传递对象和属性名
const fooRef = toRef(props, 'foo')

新语法(3.3+):

// 使用 getter 函数,更灵活
const fooRef = toRef(() => props.foo)

2.优势

类型安全,Getter 语法提供更好的 TypeScript 支持,自动推断类型。

计算属性支持,可以处理计算值:

// 计算属性形式的引用
const computedRef = toRef(() => props.foo + props.bar)

条件访问,安全地访问可能不存在的属性:

// 安全地访问嵌套属性
const safeRef = toRef(() => props?.nested?.value)

3.实际应用场景

<script setup>
import { toRef, watch } from 'vue'

const props = defineProps({
  userId: String,
  filters: Object
})

// 监听 props.userId 的变化
watch(
  toRef(() => props.userId),
  (newId) => {
    fetchUserData(newId)
  }
)

// 传递给组合式函数
useSomeFeature(toRef(() => props.foo))

// 复杂的计算逻辑
const processedData = toRef(() => {
  return props.data?.filter(item => item.active) || []
})
</script>

3.要点总结

在这个代码中,toRef(() => props.foo)创建了一个响应式引用,它:

保持与 props.foo的响应式连接

提供更好的类型推断

支持复杂的计算逻辑

是 Vue 3.3+ 推荐的做法

这种语法让代码更简洁、类型更安全,是现代 Vue 开发的最佳实践。

4.和不使用 toRef 的主要区别

// 直接传递 props.foo
useSomeFeature(props.foo)

// 使用 toRef 包装
useSomeFeature(toRef(() => props.foo))

1.响应性保持

不使用 toRef:

// 失去响应性!
useSomeFeature(props.foo)

// 在 useSomeFeature 内部:
function useSomeFeature(value) {
  watch(value, (newVal) => {
    // ❌ 不会触发!因为 value 是原始值,不是响应式引用
  })
}

使用 toRef:

// 保持响应性
useSomeFeature(toRef(() => props.foo))

function useSomeFeature(valueRef) {
  watch(valueRef, (newVal) => {
    // ✅ 会触发!当 props.foo 变化时自动响应
  })
}

2. 实际示例对比

<script setup>
import { toRef, watch, ref } from 'vue'

const props = defineProps({
  count: Number
})

// 场景1:不使用 toRef(有问题)
const count1 = props.count
watch(() => count1, (newVal) => {
  console.log('Count1 changed:', newVal) // ❌ 永远不会触发
})

// 场景2:使用 toRef(正确)
const countRef = toRef(() => props.count)
watch(countRef, (newVal) => {
  console.log('CountRef changed:', newVal) // ✅ props.count 变化时触发
})

// 场景3:直接监听 props(也可以,但不够灵活)
watch(() => props.count, (newVal) => {
  console.log('Props.count changed:', newVal) // ✅ 也会触发
})
</script>

3. 组合式函数中的差异

不使用 toRef 的问题:

// 组合式函数
function useFeature(value) {
  const internalState = ref(value) // ❌ 只获取初始值,不会更新
  
  watch(() => value, (newVal) => {
    // ❌ 如果 value 不是响应式对象,这里不会工作
  })
  
  return { internalState }
}

// 使用
useFeature(props.foo) // 响应性丢失!

使用 toRef 的正确方式:

function useFeature(valueRef) {
  // ✅ 直接使用响应式引用
  watch(valueRef, (newVal) => {
    console.log('值变化了:', newVal)
  })
  
  // 可以基于 ref 进行计算
  const doubled = computed(() => valueRef.value * 2)
  
  return { doubled }
}

// 使用
useFeature(toRef(() => props.foo)) // 保持响应性

4. 模板中的表现

<template>
  <div>
    <!-- 直接使用 props -->
    <p>直接: {{ props.count }}</p> <!-- ✅ 响应式 -->
    
    <!-- 使用 toRef -->
    <p>Ref: {{ countRef }}</p> <!-- ✅ 响应式 -->
    
    <!-- 直接传递的值 -->
    <p>普通变量: {{ normalCount }}</p> <!-- ❌ 不会更新 -->
  </div>
</template>

<script setup>
const normalCount = props.count // ❌ 只获取初始值
const countRef = toRef(() => props.count) // ✅ 保持响应式连接
</script>

5.什么时候可以不用 toRef?

1. 直接在使用处访问

// ✅ 可以直接使用,因为模板有响应式访问
<template>
  <div>{{ props.foo }}</div>
</template>

2. 在 watch 或 computed 中直接引用

// ✅ 这样是可以的
watch(() => props.foo, (newVal) => {
  // 正常工作
})

const computedVal = computed(() => props.foo * 2)

3. 需要传递响应式引用时

// ❌ 不要这样(失去响应性)
someFunction(props.foo)

// ✅ 应该这样(保持响应性)
someFunction(toRef(() => props.foo))

6.最后总结

特性

不使用 toRef

使用 toRef

响应性

可能丢失

保持

类型安全

一般

更好

组合式函数兼容

模板中使用

可以直接用

需要 .value

适用场景

简单显示、本地计算

传递引用、组合式函数

核心原则:​ 当你需要将 prop 的值传递给其他函数或需要保持响应性连接时,使用 toRef。如果只是本地使用或模板显示,可以直接使用 props.xxx


TOP