前言
在 Vue 的数据驱动 思想下,我们通常通过修改数据来驱动视图更新,避免直接操作 DOM。但在实际开发中,总会遇到一些非 DOM 不可的场景:比如获取输入框焦点、调用第三方库初始化画布、获取子组件的数据或方法等。
这时候,Vue 提供的模板引用 ref 就派上了用场。
本文将带你系统学习 Vue3 中 ref 的核心用法、操作 DOM、获取/调用子组件实例、与 reactive 区别等知识点,帮你彻底掌握模板引用,写出更优雅的 Vue 代码。
一、什么是模板引用 ref?
简单说:
ref 是 Vue 提供的获取「真实 DOM 元素」或「子组件实例」的方式。
它的核心作用:
- 获取原生 DOM 节点(如 input、div、canvas)
- 获取子组件实例(调用子组件方法、访问子组件数据)
- 配合生命周期函数执行 DOM 操作
注意:Vue3 中
ref有两个含义:
- 响应式引用:定义基础类型响应式数据
- 模板引用 :获取 DOM / 子组件
本文重点讲:模板引用 ref
二、基础用法:获取并操作 DOM
1. 使用步骤(三步)
- 给元素添加
ref="名称" - 在
<script setup>中创建一个同名 ref 对象 - 等 DOM 渲染后,通过
.value获取真实 DOM
2. 实战:获取 input 并自动聚焦
vue
<template>
<!-- 1. 给 DOM 添加 ref 属性 -->
<input type="text" ref="inputRef" placeholder="自动聚焦" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
// 2. 创建与 ref 同名的响应式变量
const inputRef = ref(null)
// 3. 在挂载完成后操作 DOM(必须在 onMounted 中)
onMounted(() => {
// 获取真实 DOM:inputRef.value
inputRef.value.focus()
console.log('真实 DOM 元素:', inputRef.value)
})
</script>
✅ 关键点:
- 模板里的
ref名称 = script 里的变量名 - 必须在
onMounted生命周期后才能获取到 DOM - 访问方式:
ref对象.value
三、进阶用法:获取子组件实例(调用方法/访问数据)
这是组件封装、组件通信 的高频用法:
父组件可以通过 ref 获取子组件实例,直接调用子组件方法、访问子组件数据。
1. 子组件(Child.vue)
必须用 defineExpose 暴露需要让父组件访问的内容!
(Vue3 脚本 setup 默认封闭,不暴露任何内容)
vue
<template>
<div>子组件内容:{{ msg }}</div>
</template>
<script setup>
import { ref } from 'vue'
// 子组件数据
const msg = ref('我是子组件数据')
// 子组件方法
const childFn = () => {
alert('子组件方法被执行了!')
}
// ✅ 必须暴露:父组件才能获取
defineExpose({
msg,
childFn
})
</script>
2. 父组件使用
vue
<template>
<button @click="callChild">调用子组件方法</button>
<!-- 给子组件添加 ref -->
<Child ref="childRef" />
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
// 子组件实例引用
const childRef = ref(null)
const callChild = () => {
// 调用子组件方法
childRef.value.childFn()
// 访问子组件数据
console.log('子组件数据:', childRef.value.msg)
}
</script>
✅ 核心规则:
- 子组件必须用
defineExpose暴露属性/方法 - 父组件通过
ref.value.xxx调用 - 不要滥用:能通过 props / emit 实现就不用 ref
四、v-for 中的多个 ref (批量获取 DOM)
v-for 循环生成的元素,不能用单个 ref,必须用函数 ref 或数组 ref。
实战:批量获取列表 DOM
vue
<template>
<ul>
<li
v-for="item in list"
:key="item"
:ref="setItemRef"
>
{{ item }}
</li>
</ul>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const list = ref([1, 2, 3, 4, 5])
// 存放所有 DOM 引用
const itemRefs = ref([])
// 函数 ref:自动把 DOM 推入数组
const setItemRef = (el) => {
if (el) {
itemRefs.value.push(el)
}
}
onMounted(() => {
console.log('所有 li DOM:', itemRefs.value)
})
</script>
五、重要注意事项(必看!避免踩坑)
1. 访问时机
- 不能在
setup同步代码中直接访问 - 必须在:
onMounted- 事件处理函数(click 等)
nextTick
2. 访问方式
- DOM / 组件实例 =
ref值.value - 不能直接用
ref值
3. 子组件必须暴露
<script setup>的组件默认封闭- 必须用
defineExpose暴露方法和数据
4. 不要滥用
- 能通过数据驱动就不操作 DOM
- 能通过 props/emit 就不调用子组件实例
六、常见面试题
1. ref 和 reactive 的区别?
ref:用于基础类型(string/number/boolean)+ DOM 引用reactive:用于引用类型(对象/数组)
2. 为什么要在 onMounted 中获取 ref?
因为 setup 执行时DOM 还没渲染,必须等挂载完成。
3. 父组件如何调用子组件方法?
- 子组件
defineExpose - 父组件用
ref获取实例 ref.value.方法名()
七、总结
- 模板 ref = 获取 DOM / 子组件实例
- 用法:
ref="名称"→ 定义同名 ref →onMounted使用 - 操作 DOM:
ref.value.focus()等 - 子组件:必须
defineExpose暴露 - v-for:使用函数 ref 批量存储
结语
模板引用 ref 是 Vue3 必备核心技能,尤其在表单操作、第三方库集成、组件封装中高频使用。
只要记住:数据驱动优先,ref 辅助,就能写出规范、高效、易维护的代码。