Vue3 模板引用实战:useTemplateRef 与 ref 的 3 种 DOM 操作模式
Vue3 模板引用实战:useTemplateRef 与 ref 的 3 种 DOM 操作模式
在 Vue3 的 Composition API 中,模板引用(Template Refs)是连接响应式数据与真实 DOM 的桥梁。本文将深入探讨如何利用useTemplateRef(Vue 3.5+)和传统ref实现三种典型场景的 DOM 操作,帮助开发者突破虚拟 DOM 的限制,直接与原生 DOM 交互。
1. 自动聚焦输入框:交互体验的起点
表单交互中,自动聚焦是提升用户体验的常见需求。传统实现需要手动调用focus(),而 Vue3 提供了更优雅的解决方案。
1.1 useTemplateRef 实现(推荐)
<script setup> import { useTemplateRef, onMounted } from 'vue' const inputRef = useTemplateRef('input-field') onMounted(() => { inputRef.value?.focus() }) </script> <template> <input ref="input-field" placeholder="自动获得焦点" /> </template>关键点解析:
useTemplateRef的参数需与模板中的ref值严格匹配- TypeScript 会自动推断
inputRef.value的类型为HTMLInputElement ?.可选链操作符避免未挂载时的空值错误
1.2 传统 ref 实现
<script setup> import { ref, onMounted } from 'vue' const inputRef = ref<HTMLInputElement | null>(null) onMounted(() => { inputRef.value?.focus() }) </script> <template> <input ref="inputRef" /> </template>对比分析:
| 特性 | useTemplateRef | 传统 ref |
|---|---|---|
| 类型推断 | 自动 | 需手动声明 |
| 命名一致性 | 强制匹配 | 自由命名 |
| 版本要求 | Vue ≥3.5 | 所有版本 |
| 模板 ref 值 | 字符串 | 变量引用 |
提示:在需要兼容旧版本或复杂命名场景下,传统 ref 更具灵活性;而在 Vue 3.5+ 项目中,
useTemplateRef能提供更好的开发体验。
2. 集成第三方图表库:突破虚拟 DOM 的限制
当集成 ECharts、D3.js 等需要直接操作 DOM 的库时,模板引用成为必要手段。下面以 ECharts 为例:
2.1 基础集成模式
<script setup> import { useTemplateRef, onMounted, watch } from 'vue' import * as echarts from 'echarts' const chartRef = useTemplateRef('chart-container') let chartInstance: echarts.ECharts | null = null onMounted(() => { if (chartRef.value) { chartInstance = echarts.init(chartRef.value) renderChart() } }) function renderChart() { chartInstance?.setOption({ xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] }, yAxis: { type: 'value' }, series: [{ data: [120, 200, 150], type: 'bar' }] }) } </script> <template> <div ref="chart-container" style="width: 600px; height: 400px;"></div> </template>2.2 响应式适配方案
当图表需要响应数据变化时:
const props = defineProps<{ data: number[] }>() watch(() => props.data, (newData) => { chartInstance?.setOption({ series: [{ data: newData }] }) }, { deep: true })性能优化技巧:
- 使用
shallowRef避免不必要的深度响应 - 通过
ResizeObserver实现容器自适应 - 在
onUnmounted中调用dispose()释放资源
import { shallowRef, onUnmounted } from 'vue' const chartInstance = shallowRef<echarts.ECharts | null>(null) onUnmounted(() => { chartInstance.value?.dispose() })3. 动态测量元素尺寸:响应式布局的核心
实现响应式布局常需要获取元素的实际尺寸。传统方案依赖全局事件,而 Vue3 的组合式 API 提供了更模块化的解决方案。
3.1 基础尺寸测量
<script setup> import { useTemplateRef, ref, onMounted } from 'vue' const containerRef = useTemplateRef('measurable') const dimensions = ref({ width: 0, height: 0 }) onMounted(() => { if (containerRef.value) { dimensions.value = { width: containerRef.value.offsetWidth, height: containerRef.value.offsetHeight } } }) </script> <template> <div ref="measurable" class="resizable-box"> 当前尺寸:{{ dimensions.width }} × {{ dimensions.height }} </div> </template>3.2 响应式尺寸监听
通过组合式函数封装可复用的尺寸监听逻辑:
// useElementSize.ts import { ref, onMounted, onUnmounted } from 'vue' export function useElementSize(targetRef: Ref<HTMLElement | null>) { const width = ref(0) const height = ref(0) let observer: ResizeObserver | null = null onMounted(() => { if (targetRef.value) { observer = new ResizeObserver((entries) => { const entry = entries[0] width.value = entry.contentRect.width height.value = entry.contentRect.height }) observer.observe(targetRef.value) } }) onUnmounted(() => { observer?.disconnect() }) return { width, height } }使用示例:
<script setup> import { useTemplateRef } from 'vue' import { useElementSize } from './useElementSize' const containerRef = useTemplateRef('responsive-container') const { width, height } = useElementSize(containerRef) </script> <template> <div ref="responsive-container"> 容器实时尺寸:{{ width }} × {{ height }} </div> </template>4. 高级模式:ref 的函数式用法
Vue3 支持更灵活的函数式 ref,特别适合动态 ref 和组件库开发场景。
4.1 动态元素引用
<script setup> import { ref } from 'vue' const items = ref([1, 2, 3]) const itemRefs = ref<HTMLElement[]>([]) function setItemRef(el: HTMLElement | null, index: number) { if (el) itemRefs.value[index] = el } </script> <template> <div v-for="(item, index) in items" :key="item"> <div :ref="el => setItemRef(el, index)">Item {{ item }}</div> </div> </template>4.2 组件方法暴露
子组件通过defineExpose暴露方法:
<!-- ChildComponent.vue --> <script setup> const sayHello = () => console.log('Hello from child!') defineExpose({ sayHello }) </script>父组件通过 ref 调用:
<script setup> import { ref } from 'vue' import ChildComponent from './ChildComponent.vue' const childRef = ref<InstanceType<typeof ChildComponent>>() function triggerChild() { childRef.value?.sayHello() } </script> <template> <ChildComponent ref="childRef" /> <button @click="triggerChild">调用子组件方法</button> </template>类型安全提示:
- 使用
InstanceType<typeof Component>获取组件实例类型 - 通过
defineExpose明确暴露的接口 - 可选链操作符避免未定义错误
在实际项目中,合理选择模板引用模式能显著提升代码的可维护性。对于简单场景,useTemplateRef提供了开箱即用的便利;复杂场景下,组合式函数封装和函数式 ref 能带来更大的灵活性。
