1. 什么是 ref
ref是 Vue 中的一个特殊 attribute,用于给元素或子组件注册引用信息。引用信息会被注册在父组件的$refs对象上。
核心作用
- DOM 元素访问:在普通 DOM 元素上使用时,引用指向 DOM 元素本身
- 组件实例访问:在子组件上使用时,引用指向组件的实例
重要特性
ref是作为渲染结果被创建的,在初始渲染时不能访问(此时还不存在)$refs不是响应式的,不应该在模板中做数据绑定- 在使用
v-for时,引用会是一个包含 DOM 节点或组件实例的数组
2. 基本用法
2.1 在组合式 API 中使用(Vue 3.5+)
使用useTemplateRef()辅助函数:
<script setup> import { useTemplateRef, onMounted } from 'vue' // 第一个参数必须与模板中的 ref 值匹配 const input = useTemplateRef('my-input') onMounted(() => { input.value.focus() }) </script> <template> <input ref="my-input" /> </template>2.2 在组合式 API 中使用(Vue 3.5 前)
<script setup> import { ref, onMounted } from 'vue' // 声明一个 ref 来存放该元素的引用 // 必须和模板里的 ref 同名 const input = ref(null) onMounted(() => { input.value.focus() }) </script> <template> <input ref="input" /> </template>2.3 在选项式 API 中使用
<script> export default { mounted() { this.$refs.input.focus() } } </script> <template> <input ref="input" /> </template>3. 具体应用场景
3.1 操作 DOM 元素
场景:在组件挂载后自动聚焦到输入框
<script setup> import { ref, onMounted } from 'vue' const inputRef = ref(null) onMounted(() => { inputRef.value.focus() }) </script> <template> <input ref="inputRef" placeholder="请输入内容" /> </template>3.2 访问子组件实例
场景:父组件调用子组件的方法
<!-- Child.vue --> <script setup> import { ref } from 'vue' const count = ref(0) const increment = () => { count.value++ } // 暴露方法给父组件 defineExpose({ increment, count }) </script> <template> <div>子组件计数:{{ count }}</div> </template><!-- Parent.vue --> <script setup> import { ref, onMounted } from 'vue' import Child from './Child.vue' const childRef = ref(null) onMounted(() => { // 调用子组件的方法 childRef.value.increment() console.log('子组件计数:', childRef.value.count) }) </script> <template> <Child ref="childRef" /> </template>3.3 处理 v-for 中的引用
场景:获取一组 DOM 元素或组件实例
<script setup> import { ref, onMounted } from 'vue' const itemRefs = ref([]) onMounted(() => { // itemRefs.value 是一个包含所有 li 元素的数组 console.log(itemRefs.value.length) // 访问第一个元素 if (itemRefs.value[0]) { itemRefs.value[0].style.color = 'red' } }) </script> <template> <ul> <li v-for="item in 5" :key="item" ref="itemRefs"> 项目 {{ item }} </li> </ul> </template>4. 注意事项
4.1 引用的生命周期
- 挂载前:
ref值为null - 挂载后:
ref值为 DOM 元素或组件实例 - 卸载后:
ref值为null(例如通过v-if控制)
4.2 响应式处理
如果需要监听引用的变化,应考虑到其值为null的情况:
<script setup> import { ref, watchEffect } from 'vue' const inputRef = ref(null) watchEffect(() => { if (inputRef.value) { inputRef.value.focus() } else { // 元素未挂载或已卸载 console.log('元素未就绪') } }) </script>4.3 组件暴露控制
- 使用
<script setup>的组件:默认是私有的,需要通过defineExpose显式暴露属性和方法 - 使用选项式 API 的组件:可以通过
expose选项限制可访问的属性和方法
// 选项式 API 中限制暴露 export default { expose: ['publicData', 'publicMethod'], data() { return { publicData: 'foo', privateData: 'bar' } }, methods: { publicMethod() { /* ... */ }, privateMethod() { /* ... */ } } }5. 类型标注(TypeScript)
5.1 为 DOM 元素标注类型
<script setup lang="ts"> import { ref, onMounted } from 'vue' const inputRef = ref<HTMLInputElement | null>(null) onMounted(() => { inputRef.value?.focus() }) </script>5.2 为组件标注类型
<script setup lang="ts"> import { ref, onMounted } from 'vue' import Child from './Child.vue' const childRef = ref<InstanceType<typeof Child> | null>(null) onMounted(() => { childRef.value?.increment() }) </script>6. 最佳实践
- 优先使用 props 和 emit:大多数情况下,应该使用标准的 props 和 emit 接口来实现父子组件交互
- 仅在必要时使用 ref:ref 会创建父子组件之间的紧密耦合,应谨慎使用
- 避免在模板中使用 $refs:$refs 不是响应式的,不适合在模板中进行数据绑定
- 合理处理 null 值:在访问 ref 的值时,始终考虑其可能为 null 的情况
- 使用 TypeScript 类型标注:提高代码的可维护性和类型安全性
7. 常见问题与解决方案
7.1 问题:ref 值为 undefined
原因:在组件初始渲染时访问 ref,此时元素还未挂载
解决方案:在onMounted生命周期钩子中访问 ref,或使用watchEffect监听 ref 的变化
7.2 问题:v-for 中 ref 不是数组
原因:在某些 Vue 版本中,v-for 中的 ref 需要特殊处理
解决方案:使用函数式 ref 或确保正确的 ref 赋值方式
<template> <ul> <li v-for="item in 5" :key="item" :ref="el => el && itemRefs.push(el)"> 项目 {{ item }} </li> </ul> </template>7.3 问题:无法访问子组件的方法
原因:子组件使用了<script setup>但未通过defineExpose暴露方法
解决方案:在子组件中使用defineExpose显式暴露需要访问的属性和方法
8. 总结
ref是 Vue 中用于直接访问 DOM 元素和组件实例的强大工具,合理使用可以简化某些场景下的开发工作。但需要注意其使用时机和最佳实践,避免过度依赖导致代码耦合度增加。
通过本文的学习,你应该掌握了:
ref的基本概念和作用- 在不同 API 风格中的使用方法
- 各种应用场景的具体实现
- 注意事项和最佳实践
- 常见问题的解决方案