Vue: 模板引用 (Template Refs)
概述
Vue 的声明式渲染模型抽象了大部分 DOM 操作,但在某些情况下仍需直接访问 DOM 元素或组件实例。模板引用 (ref
) 提供了这种能力,允许我们在组件挂载后获得对特定 DOM 元素或子组件实例的直接引用。
基础用法
在模板中声明引用
<template><input ref="myInput">
</template>
在组合式 API 中访问引用
<script setup>
import { useTemplateRef, onMounted } from 'vue'// 声明引用,参数必须与模板中的 ref 值匹配
const myInput = useTemplateRef('myInput')onMounted(() => {// 在挂载后访问引用myInput.value.focus()
})
</script>
模板引用生命周期流程图
组件上的引用
引用子组件实例
<script setup>
import { useTemplateRef, onMounted } from 'vue'
import Child from './Child.vue'const childRef = useTemplateRef('child')onMounted(() => {// childRef.value 持有 <Child /> 的实例console.log(childRef.value) // 子组件实例
})
</script><template><Child ref="child" />
</template>
子组件暴露接口
<!-- Child.vue -->
<script setup>
import { ref } from 'vue'const a = 1
const b = ref(2)// 显式暴露属性和方法
defineExpose({a,b,someMethod() {// 方法实现}
})
</script>
v-for 中的模板引用
基本用法
<script setup>
import { ref, useTemplateRef, onMounted } from 'vue'const list = ref([1, 2, 3])
const itemRefs = useTemplateRef('items')onMounted(() => {// itemRefs.value 是一个数组,包含所有列表项元素console.log(itemRefs.value) // [li, li, li]
})
</script><template><ul><li v-for="item in list" ref="items">{{ item }}</li></ul>
</template>
函数模板引用
使用函数处理引用
<script setup>
import { ref } from 'vue'const inputRef = ref(null)// 引用处理函数
const setInputRef = (el) => {inputRef.value = elif (el) {console.log('元素已挂载:', el)} else {console.log('元素已卸载')}
}
</script><template><input :ref="setInputRef"><!-- 或者使用内联函数 --><input :ref="(el) => { inputRef.value = el }">
</template>
类型定义 (TypeScript)
为模板引用标注类型
<script setup lang="ts">
import { useTemplateRef, onMounted } from 'vue'// Vue 会自动推断类型,但也可以显式标注
const input = useTemplateRef<HTMLInputElement>('my-input')onMounted(() => {if (input.value) {input.value.focus() // TypeScript 知道这是一个 HTMLInputElement}
})
</script><template><input ref="my-input" />
</template>
为组件引用标注类型
<script setup lang="ts">
import { useTemplateRef, onMounted } from 'vue'
import Child from './Child.vue'// 定义子组件实例类型
type ChildInstance = InstanceType<typeof Child>const childRef = useTemplateRef<ChildInstance>('child')onMounted(() => {if (childRef.value) {// 可以访问子组件暴露的属性和方法console.log(childRef.value.a)}
})
</script>
最佳实践与注意事项
1. 引用访问时机
<script setup>
import { useTemplateRef, onMounted, watchEffect } from 'vue'const input = useTemplateRef('my-input')// 正确:在挂载后访问
onMounted(() => {if (input.value) {input.value.focus()}
})// 使用 watchEffect 处理引用变化
watchEffect(() => {if (input.value) {// 元素已挂载console.log('元素可用:', input.value)} else {// 元素未挂载或已卸载console.log('元素不可用')}
})
</script>
2. 条件渲染中的引用
<template><div><button @click="showInput = !showInput">切换显示</button><input v-if="showInput" ref="myInput"></div>
</template><script setup>
import { useTemplateRef, watch } from 'vue'const showInput = ref(false)
const myInput = useTemplateRef('my-input')// 监听条件变化
watch(showInput, (newVal) => {if (newVal) {// 需要等待下一次 DOM 更新nextTick(() => {if (myInput.value) {myInput.value.focus()}})}
})
</script>
3. 引用管理策略
场景 | 推荐方法 | 注意事项 |
---|---|---|
单个元素引用 | useTemplateRef | 确保名称匹配 |
动态元素引用 | 函数引用 | 处理卸载情况 |
列表引用 | v-for + ref | 引用值为数组 |
组件引用 | 组件ref + defineExpose | 避免过度暴露 |
总结
核心概念
模板引用提供直接访问 DOM 元素和组件实例的能力
使用
useTemplateRef()
在组合式 API 中获取引用引用值在组件挂载前为
null
,挂载后指向实际元素/实例组件引用需要通过
defineExpose
显式暴露接口
适用场景
焦点管理
动画触发
第三方库集成
测量元素尺寸/位置
需要直接 DOM 操作的特定情况
注意事项
避免过度使用引用,优先使用声明式方法
注意引用访问时机,确保元素已挂载
处理条件渲染中的引用变化
组件引用应通过 props/emit 优先,引用作为最后手段
通过合理使用模板引用,可以在保持 Vue 声明式优势的同时,处理需要直接 DOM 操作的边缘情况。