Vue.js: Refs 与 DOM 操作
最后更新:2026-10-20
Template Refs 让你直接访问 DOM 元素或子组件实例——比如调用 input.focus()、访问组件方法。Vue 3.5 引入了更强大的 useTemplateRef 组合式 API,配合 TypeScript 类型推断。
Template Refs 是"应急通道"——Vue 推荐用 ref/reactive/props/emit 解决大部分问题,Template Refs 只在需要直接操作 DOM 或组件实例时用。
1. 你将学到
ref="el"模板引用基础useTemplateRef(Vue 3.5+ 推荐)- 访问 DOM 元素(focus、scrollIntoView 等)
- 访问子组件实例(defineExpose 暴露方法)
$refs替代与 Composition API 写法- v-for 中 ref 数组
- 5 大使用场景和 4 个反模式
2. 一个登录表单的"自动聚焦"难题
(1) 痛点:modal 打开后 input 怎么自动 focus?
Alice 构建了一个登录弹窗,需要自动聚焦用户名输入框:
JS
// ❌ 翻车版:直接 querySelector
onMounted(() => {
const input = document.querySelector('.username-input')
input.focus() // ❌ 不符合 Vue 理念
})
Vue 理念:避免直接操作 DOM。改用 Template Refs。
产品经理 Charlie:
"Alice,弹窗打开后,用户名输入框应该自动聚焦,这样用户就能直接开始输入。"
(2) Vue Template Refs 解法
(3) 收益
使用 Template Refs 后:
- 代码可读性:明确"我要 ref 这个元素"
- 符合 Vue 理念:不直接操作 DOM
- 类型安全:TypeScript 推断元素类型
- 生命周期正确:在 onMounted 或 nextTick 中访问
3. ref 基础用法
(1) 字符串 ref(Vue 2 风格,已废弃)
JS
// ❌ 不推荐:字符串 ref
export default {
mounted() {
this.$refs.input.focus()
}
}
(2) 变量 ref(Vue 3 推荐)
(3) 5 大基本操作
JS
// 1. 访问 DOM 元素
inputRef.value // <input> 元素
// 2. 调用 DOM API
inputRef.value.focus()
inputRef.value.blur()
inputRef.value.select()
inputRef.value.scrollIntoView()
// 3. 读取/修改 DOM 属性
inputRef.value.value // input 的 value
inputRef.value.disabled // disabled 属性
inputRef.value.style.color = 'red' // 修改样式
// 4. 监听 DOM 事件(不推荐,用 @event)
inputRef.value.addEventListener('focus', handler)
// 5. 访问子组件实例(详见 17.4)
childRef.value.someMethod()
▶ 示例:登录表单自动聚焦
▶ 试一试输出:
TEXT
表单里两个输入框(占位符 Username / Password)和一个绿色「Login」按钮,下方灰色小字「打开页面自动聚焦到 Username 输入框」。
页面打开时光标已经在 Username 输入框里,可以直接打字。
填完点 Login → 控制台打印「Login: 用户名 密码」,页面不会刷新(@submit.prevent)。
▶ 示例:自动滚动到底部(聊天框)
▶ 试一试输出:
TEXT
一个固定 150px 高、可滚动的消息框,初始 3 条:「👋 欢迎来到聊天室」「这是历史消息 2」「这是历史消息 3」,下面是输入框和「发送」按钮。
输入文字后点「发送」或按回车 → 新消息追加到列表末尾、输入框清空、消息框自动滚到最底部。
输入为空时点发送不会有任何变化。
4. useTemplateRef(Vue 3.5+ 推荐)
(1) 为什么需要 useTemplateRef?
变量 ref 在 <script setup> 中需要起 2 个名字(模板 ref + 变量),容易不一致。useTemplateRef 用一个名字搞定,TypeScript 推断更强。
(2) 5 大优势
| 优势 | 说明 |
|---|---|
| 类型推断 | TypeScript 自动知道是 HTMLInputElement |
| 重命名安全 | 改一处名字 IDE 同步 |
| 避免名字错配 | 模板 ref 和变量名不一致会报错 |
| 简化 setup | 无需 const inputRef = ref(null) |
| 更好的 DevTools | Vue DevTools 5.x 支持 |
(3) 完整对比
VUE
<!-- 旧写法:变量 ref -->
<template>
<input ref="usernameInput">
</template>
<script setup>
const { ref, onMounted } = Vue
const inputRef = ref(null) // 名字可能和模板不一致
onMounted(() => inputRef.value.focus())
</script>
<!-- 新写法:useTemplateRef(Vue 3.5+ 推荐)-->
<template>
<input ref="usernameInput">
</template>
<script setup>
const { useTemplateRef, onMounted } = Vue
const inputRef = useTemplateRef('usernameInput') // 一个名字搞定
onMounted(() => inputRef.value.focus())
</script>
5. 访问子组件实例
(1) defineExpose 暴露方法
(2) 父组件访问
VUE
<template>
<MyInput ref="myInputRef" v-model="searchQuery" />
<button @click="focusInput">Focus Input</button>
</template>
<script setup>
const { ref } = Vue
import MyInput from './MyInput.vue'
const searchQuery = ref('')
const myInputRef = ref(null)
function focusInput() {
myInputRef.value.focus() // 调用子组件暴露的方法
myInputRef.value.select() // 也可以链式调用
}
</script>
(3) 5 大使用场景
| 场景 | 子组件暴露 | 父组件调用 |
|---|---|---|
| 表单聚焦 | focus() |
inputRef.focus() |
| 清空表单 | clear() |
formRef.clear() |
| 重新加载数据 | reload() |
tableRef.reload() |
| 打开弹窗 | open() |
modalRef.open() |
| 提交表单 | submit() |
formRef.submit() |
6. v-for 中的 ref 数组
(1) 基础用法
(2) 动态 ref(v-for 动态数量)
(3) 5 大注意事项
| 注意点 | 说明 |
|---|---|
| 数组顺序 | 与 v-for 数据顺序一致 |
| 响应式 | ref 数组变化需要 watch |
| 条件渲染 | v-if 后 ref 可能不更新 |
| 动态数量 | 用对象或函数式 ref |
| 性能 | 大量 ref(100+)会拖慢渲染 |
7. 综合演练:父子组件 ref 校验
▶ 示例:父组件调用子组件方法(defineExpose)
▶ 展开运行示例(69 行)
输出:
TEXT
页面上是两个输入框(Email、Password (≥6位))和「Submit」按钮,初始没有任何提示文字。
空着点 Submit → 出现红色「❌ Invalid email」,光标跳到 Email 框。
填 a@b.com、密码填 123 再提交 → 红色「❌ Invalid password」;邮箱和 6 位以上密码都填好 → 出现绿色「✅ Validation passed!」。
(1) 📊 5 个常见错误速查
| 错误 | 现象 | 解决 |
|---|---|---|
| 模板 ref 拼错 | ref.value 是 null | 检查 ref 名字一致 |
| 在 setup 顶层访问 | ref.value 是 null | 用 onMounted |
| 弹窗内访问 | 不工作 | 用 nextTick 等 DOM |
| v-for ref 数组访问 | 索引错位 | 用 :key 稳定顺序 |
| 跨组件 ref | undefined | 子组件用 defineExpose |
(2) 📊 5 大 ref 类型对比
| 类型 | 例子 | 适用 |
|---|---|---|
| DOM 元素 | ref="inputRef" → HTMLInputElement |
访问 input/div |
| 组件实例 | ref="childRef" → 组件实例 |
调子组件方法 |
| v-for 数组 | ref="itemRefs" → Array |
列表项访问 |
| 函数式 ref | :ref="el => ..." → 单个元素 |
动态数量 |
| 字符串 ref | ref="name" → this.$refs |
Vue 2 风格(不推荐) |
❓ 常见问题
Q 什么时候用 Template Refs?
A 4 种情况:(1) DOM 操作(focus/scroll/canvas);(2) 调子组件方法;(3) 集成第三方库(ECharts/Mapbox);(4) 测量元素大小。其他场景用 ref/reactive/props。
Q useTemplateRef 必须在 Vue 3.5+ 用吗?
A 是的。Vue 3.5+ 才支持。Vue 3.4 及以下用变量 ref。TypeScript 类型推断更优。
Q Template Refs 和 useRef(React)区别?
A React useRef 返回 mutable ref,Vue 模板 ref 返回 ref 对象。Vue 3.5+ useTemplateRef 更接近 React useRef 的 API。
Q 子组件需要 expose 哪些方法?
A 只 expose 父组件真的需要的方法(如 focus / clear / validate)。其他内部方法不要 expose(封装原则)。
Q v-for 中的 ref 数组什么时候更新?
A 每次 v-for 重新渲染时更新。v-if 切换也会更新。watch ref 数组可以响应变化。
Q Template Refs 和 provide/inject 冲突吗?
A 不冲突。Template Refs 用于"父访问子",provide/inject 用于"跨层级共享数据"。可组合使用。
Q 怎么在 TypeScript 中给 ref 加类型?
A 用
useTemplateRef<HTMLInputElement>('usernameInput')。或 ref<HTMLInputElement | null>(null)。📖 小节
- Template Refs 用于直接访问 DOM 元素或子组件实例
- 3 种写法:变量 ref(Vue 3)/ useTemplateRef(Vue 3.5+ 推荐)/ 字符串 ref(Vue 2 风格,不推荐)
- 5 大基本操作:focus / scroll / select / 改属性 / 调子组件
- defineExpose 让子组件暴露方法给父
- v-for 中 ref 自动收集到数组
- 5 大场景:自动聚焦 / 滚动到底 / 调子方法 / 集成第三方 / 测元素大小
- 4 个反模式:拼错 / 顶层访问 / 弹窗不用 nextTick / 跨组件 ref
📝 作业
-
基础题(难度⭐) 实现一个简单的自动聚焦表单:
- 1 个 input + 1 个 button
- 页面加载后 input 自动 focus
- 点击 button 后 button 文字改为 "Submitted" 并 disable
-
进阶题(难度⭐⭐) 实现一个自动滚动到底部的聊天框:
- 消息列表(div 容器)
- 输入框 + 发送按钮
- 发送后消息添加到列表,自动滚动到底
- v-for 渲染消息,ref 数组管理 DOM
-
挑战题(难度⭐⭐⭐) 实现完整的"父子组件 + Template Refs"系统:
- FormValidator 子组件:暴露 validate() 和 reset() 方法
- LoginPage 父组件:调用 validate 校验,失败显示错误
- 5 个 input 字段(username/email/password/phone/captcha)
- useTemplateRef(Vue 3.5+)+ TypeScript 强类型
- 弹窗打开时自动 focus 第一个 input
- 失败时 focus 到第一个错误字段