Appearance
除了基础的 ref() / reactive(),Vue 还提供了一批 ref 工具函数 来处理特殊场景。
isRef / unref — 类型判断与自动解包
vue
<script setup>
import { ref, isRef, unref } from 'vue'
const count = ref(0)
const plain = 42
console.log(isRef(count)) // true
console.log(isRef(plain)) // false
// unref:如果是 ref 则返回 .value,否则返回原始值
console.log(unref(count)) // 0
console.log(unref(plain)) // 42
// 等价于:
function myUnref(r) {
return isRef(r) ? r.value : r
}
</script>
适用于工具函数——你不想限制参数必须是 ref 还是普通值:
vue
<script setup>
function formatMsg(msg) {
return `消息:${unref(msg)}`
}
const msgRef = ref('Hello')
console.log(formatMsg(msgRef)) // 消息:Hello
console.log(formatMsg('World')) // 消息:World
</script>
shallowRef — 浅响应优化
ref 会递归地把整个数据变成响应式。如果数据很大或不需要深层响应,用 shallowRef 省性能。
vue
<script setup>
import { shallowRef, triggerRef } from 'vue'
const logs = shallowRef([])
// 直接推入元素:数组变了,但 shallowRef 没检测到
logs.value.push('新日志') // ❌ 不触发更新
// 替换整个引用:检测到
logs.value = [...logs.value, '新日志'] // ✅ 触发更新
// 或者用 triggerRef 手动通知
logs.value.push('新日志')
triggerRef(logs) // ✅ 手动触发更新
</script>
典型场景
| 场景 | 说明 | | 大型列表 | 几千条日志、表格数据,不需要逐条追踪 | | 第三方数据 | 从外部库拿到的不可变数据 | | 频繁替换 | 每次都重新赋值整个对象 |
shallowRef+triggerRef搭配使用,兼顾性能和灵活性。
customRef — 自定义 ref
customRef 让你完全控制 ref 的依赖追踪和触发更新时机。最常见的场景是防抖:
vue
<script setup>
import { customRef } from 'vue'
function debouncedRef(value, delay = 300) {
let timer
return customRef((track, trigger) => {
return {
get() {
track() // 告诉 Vue:这个值被依赖了
return value
},
set(newValue) {
clearTimeout(timer)
timer = setTimeout(() => {
value = newValue
trigger() // 告诉 Vue:值变了,通知依赖方
}, delay)
}
}
})
}
const keyword = debouncedRef('', 500) // 500ms 防抖
</script>
<template>
<input v-model="keyword" placeholder="防抖搜索" />
</template>
customRef 接收一个工厂函数,参数是 track 和 trigger:
track()— 在get里调用,标记依赖追踪trigger()— 在set里调用,触发更新通知
useTemplateRef — 模板引用新写法(Vue 3.5+)
Vue 3.5 引入了 useTemplateRef,统一了模板 ref 的声明方式,不再要求变量名和模板 ref 名一致。
vue
<script setup>
import { useTemplateRef, onMounted } from 'vue'
const input = useTemplateRef('myInput')
// 等价于 <script setup> 中 const input = ref(null) + ref="myInput"
onMounted(() => {
input.value?.focus()
})
</script>
<template>
<input ref="myInput" type="text" />
</template>
优点:
- 变量名和 ref 名可以不同
- 类型推导更好
- 语义清晰:一看就知道是模板引用
在 v-for 中使用
vue
<script setup>
import { useTemplateRef, onMounted } from 'vue'
const items = useTemplateRef('items')
onMounted(() => {
console.log(items.value) // HTMLElement[]
})
</script>
<template>
<li v-for="item in list" :key="item" ref="items">{{ item }}</li>
</template>
defineExpose — 控制子组件暴露的内容
父组件通过 ref 获取子组件实例时,默认什么都拿不到(<script setup> 是关闭的)。 需要用 defineExpose 显式暴露:
vue
<!-- Child.vue -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
const internal = ref('秘密') // 不暴露
function reset() { count.value = 0 }
defineExpose({ count, reset })
</script>
vue
<!-- Parent.vue -->
<script setup>
import { useTemplateRef } from 'vue'
import Child from './Child.vue'
const childRef = useTemplateRef('child')
function handleClick() {
childRef.value?.reset()
console.log(childRef.value?.count)
}
</script>
<template>
<Child ref="child" />
<button @click="handleClick">重置</button>
</template>
选项式 API 中,
$refs会自动暴露所有data和methods,不需要defineExpose。
函数式 ref
ref 属性也可以传一个函数,在元素挂载或卸载时调用:
vue
<script setup>
import { ref } from 'vue'
const divEl = ref(null)
function setRef(el) {
divEl.value = el
console.log('元素挂载了', el)
}
</script>
<template>
<div :ref="setRef">内容</div>
</template>
函数接收元素本身作为参数,卸载时传 null。适合在需要动态处理 ref 时使用。
TypeScript 类型安全
ref 类型
vue
<script setup lang="ts">
import { ref, type Ref } from 'vue'
const count = ref<number>(0)
const user = ref<{ name: string; age: number } | null>(null)
// 显式标注 Ref 类型(不常用)
const message: Ref<string> = ref('hello')
</script>
组件 ref 类型
用 InstanceType 获取组件实例类型:
vue
<script setup lang="ts">
import { useTemplateRef } from 'vue'
import MyInput from './MyInput.vue'
type MyInputInstance = InstanceType<typeof MyInput>
const inputRef = useTemplateRef<MyInputInstance>('input')
onMounted(() => {
inputRef.value?.focus()
})
</script>
<template>
<MyInput ref="input" />
</template>
或者用 typeof + defineExpose 配合:
vue
<script setup lang="ts">
// 只声明暴露的类型,不暴露实现细节
defineExpose({
count: ref(0),
reset: () => {}
})
</script>
模板 ref 类型
vue
<script setup lang="ts">
const divRef = useTemplateRef<HTMLDivElement>('div')
const inputRef = useTemplateRef<HTMLInputElement>('input')
const canvasRef = useTemplateRef<HTMLCanvasElement>('canvas')
</script>
<template>
<div ref="div">块</div>
<input ref="input" />
<canvas ref="canvas"></canvas>
</template>
速查表
| API | 用途 | 层级 | | isRef() | 判断是否是 ref | 工具 | | unref() | 自动解包(ref 取 .value,否则原值) | 工具 | | shallowRef() | 浅响应,大对象性能优化 | 响应式 | | triggerRef() | 手动触发 shallowRef 更新 | 响应式 | | customRef() | 自定义追踪/触发逻辑(防抖等) | 自定义 | | useTemplateRef() | 声明模板引用(Vue 3.5+) | 模板 | | defineExpose() | 控制组件暴露给父级的属性 | 组件 | | 函数式 :ref | 动态处理 ref 回调 | 模板 | 📝 Note 基础 ref() 用法见 ,模板 ref 基础见 。