Vue 强调声明式编程,但现实中,仍有一些场景必须直接操作 DOM 或访问子组件,比如:
-
页面加载时让某个 自动聚焦;
-
通过第三方库(如图表、动画)操作某个 DOM 元素;
-
让父组件控制子组件的内部方法或数据。
这时候,Vue 提供了一个优雅的解决方案 ------ 模板引用(template ref)。
本文将从基础到进阶,一步步讲透 Vue 中的 ref="xxx" 是如何工作的,特别是在组合式 API 和 Vue 3.5 中的新能力。
✨ 什么是模板引用?
模板引用是给 DOM 元素或组件打一个"标签",在 JavaScript 中就可以拿到它的真实引用。
js
<template>
<input ref="myInput" />
</template>
通过 ref,我们就能在 JS 中这样访问这个 DOM 元素。
🧱 一、在组合式 API 中如何访问模板引用?
Vue 3.5 起推荐使用 useTemplateRef() 辅助函数:
js
<script setup>
import { useTemplateRef, onMounted } from 'vue'
const input = useTemplateRef('my-input')
onMounted(() => {
input.value.focus() // 自动聚焦
})
</script>
<template>
<input ref="my-input" />
</template>
👀 说明:
- input.value 就是 [ ] 这个 DOM 节点;
- 必须在 onMounted() 后访问它,因为挂载前元素还未插入 DOM;
- 类型会自动根据 ref 绑定的 HTML 元素推断(适用于 TS 开发者)。
⏳ 二、挂载时机 & null 值
⚠️ 在 DOM 未挂载前,ref 是 null,你不能提前在
使用 watchEffect 来安全处理:
js
watchEffect(() => {
if (input.value) {
input.value.focus()
} else {
// 尚未挂载,或已卸载(比如通过 v-if 控制)
}
})
这种写法适用于:元素动态插入、被 v-if 控制显示等复杂情况。
🧩 三、组件上的模板引用
不仅可以引用 DOM 元素,也可以直接引用子组件实例:
js
<script setup>
import Child from './Child.vue'
import { useTemplateRef, onMounted } from 'vue'
const childRef = useTemplateRef('child')
onMounted(() => {
console.log('子组件实例:', childRef.value)
})
</script>
<template>
<Child ref="child" />
</template>
🧱 父组件能访问子组件的哪些内容?
- 若子组件使用的是选项式 API,父组件几乎能访问它的所有属性和方法;
- 若子组件使用 :
js
<!-- Child.vue -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
const sayHi = () => console.log('Hello from child!')
defineExpose({ count, sayHi }) // 手动暴露
</script>
📦 四、在 v-for 中使用模板引用(Vue 3.5+)
Vue 3.5 开始,v-for 语境下的 ref 会自动聚合成数组!
js
<script setup>
import { useTemplateRef, onMounted, ref } from 'vue'
const list = ref(['A', 'B', 'C'])
const itemRefs = useTemplateRef('items')
onMounted(() => {
console.log('所有 <li> 元素:', itemRefs.value)
})
</script>
<template>
<ul>
<li v-for="item in list" :key="item" ref="items">
{{ item }}
</li>
</ul>
</template>
🧠 注意事项:
- itemRefs.value 是一个 DOM 元素数组;
- 每个元素在挂载后才可访问;
- 需使用唯一的 :key 以保持顺序一致。
🔁 五、函数形式模板引用(更灵活)
除了字符串名,你还可以使用函数形式绑定模板引用:
js
<script setup>
import { ref } from 'vue'
const inputEl = ref(null)
function handleRef(el) {
inputEl.value = el
}
</script>
<template>
<input :ref="handleRef" />
</template>
函数会在:
-
元素挂载时被调用(传入元素本身);
-
元素卸载时再调用一次(传入 null);
✅ 优点:
- 更灵活;
- 可以直接绑定到组件方法;
- 在你需要动态分发或集中管理多个引用时很好用。
🧪 六、实战:打开模态框时自动聚焦输入框
js
<script setup>
import { ref, watch } from 'vue'
import { useTemplateRef } from 'vue'
const show = ref(false)
const inputRef = useTemplateRef('input')
watch(show, (visible) => {
if (visible && inputRef.value) {
inputRef.value.focus()
}
})
</script>
<template>
<button @click="show = true">打开模态框</button>
<div v-if="show" class="modal">
<input ref="input" placeholder="请输入..." />
</div>
</template>
🎯 使用模板引用的最佳实践
用法场景 | 推荐方式 |
---|---|
访问 DOM 元素 | useTemplateRef('name') |
动态 DOM(v-if/v-for) | 配合 watchEffect 或回调式 ref |
获取子组件实例 | + defineExpose |
多个元素(v-for) | 3.5+ 中 ref 自动聚合为数组 |
动态绑定 | 使用函数形式的 :ref="fn" |
🔚 总结
模板引用是 Vue 提供的一种对底层的"逃逸口",但它不应该成为主流手段。只有在以下场景中推荐使用:
✅ 需要访问 DOM 元素(如获取焦点、测量宽高)
✅ 第三方库初始化依赖 DOM
✅ 父组件需要精细控制子组件行为(用 defineExpose)
✅ 需要动态聚合 v-for 中的元素
其余场景,我们更推荐你使用:
- props 和 emit 来传递数据;
- computed、watch 来处理响应;
- 插槽(slot)进行结构灵活扩展。