最近升级 Vue3.5 后,发现了 useTemplateRef 这个宝藏 API,直接解决了之前用传统 ref 封装 DOM 逻辑时的痛点 ------ 终于能把「获取 DOM + 操作 DOM」的逻辑彻底抽离,全项目复用了!
之前写业务的时候总遇到这种情况:多个组件需要自动聚焦、监听元素尺寸,用传统 ref 封装 Hook 时特别别扭,要么得让组件里的 ref 变量名和 Hook 里保持一致,要么就得写一堆冗余代码传参。直到用了 useTemplateRef 才发现,原来 DOM 逻辑复用可以这么丝滑。
先说说核心区别:为啥传统 ref 复用起来那么麻烦?
之前用 ref(null) 封装 Hook 时,踩过很多坑。比如想写个自动聚焦的通用逻辑,Hook 里定义了 const inputEl = ref(null),那使用这个 Hook 的组件,模板里的 input 必须绑定 :ref="inputEl"------ 这就意味着组件得知道 Hook 内部的变量名,完全没法灵活复用。
而且组件里还得手动接收 Hook 导出的变量,代码又冗余又耦合。如果多个组件用这个 Hook,一旦想改 Hook 里的变量名,所有组件都得跟着改,维护成本太高了。
而 useTemplateRef 最妙的地方在于,不用管组件里的变量名,直接在 Hook 里固定一个字符串标识,组件模板只要对应加上这个 ref 名就行,逻辑完全解耦。
实战两个常用 Hook:看完直接抄去用
分享两个我最近封装的实战 Hook,都是业务中高频用到的,现在全项目直接复用,不用写重复代码。
1. 自动聚焦 Hook:useAutoFocus
之前每个需要自动聚焦的输入框,都得写一遍 onMounted + ref,现在封装一次就行:
js
// useAutoFocus.js
import { useTemplateRef, onMounted } from 'vue'
export function useAutoFocus() {
// 直接在 Hook 里指定 ref 名:'auto-focus'
const inputEl = useTemplateRef('auto-focus')
onMounted(() => {
// 挂载后自动聚焦,可选链避免报错
inputEl.value?.focus()
})
return { inputEl }
}
用的时候特别简单,组件里不用写任何逻辑,只要给 input 加个对应的 ref 就行:
js
<script setup> // 直接引入复用,不用写任何ref、聚焦逻辑
import { useAutoFocus } from './useAutoFocus'
useAutoFocus()
</script>
<template>
<!-- 只需要给元素加 ref="auto-focus" -->
<input ref="auto-focus" placeholder="自动聚焦" />
</template>
不管是登录页、搜索框还是表单输入框,只要引入这个 Hook,加个 ref="auto-focus",立马实现自动聚焦,完全不用关心内部逻辑。
2. DOM 尺寸监听 Hook:useElementSize
监听元素宽高变化也是个高频需求,比如响应式布局、图表自适应,之前每次都要写监听 resize 事件、清理监听,现在封装后直接复用:
js
// useElementSize.js
import { useTemplateRef, ref, onMounted, onUnmounted } from 'vue'
export function useElementSize() {
// 绑定 ref 标识:'resize-el'
const el = useTemplateRef('resize-el')
const width = ref(0)
const height = ref(0)
// 更新元素尺寸的方法
const updateSize = () => {
if (el.value) {
width.value = el.value.offsetWidth
height.value = el.value.offsetHeight
}
}
onMounted(() => {
// 初始获取一次尺寸
updateSize()
// 监听窗口 resize 事件
window.addEventListener('resize', updateSize)
})
onUnmounted(() => {
// 组件卸载时清理监听,避免内存泄漏
window.removeEventListener('resize', updateSize)
})
return { width, height }
}
组件使用时,只需要给要监听的元素加个 ref="resize-el",直接获取宽高变量:
js
<script setup>
import { useElementSize } from './useElementSize'
// 直接复用DOM尺寸监听
const { width, height } = useElementSize()
</script>
<template>
<!-- 只需标记 ref="resize-el" -->
<div ref="resize-el">
宽度:{{ width }} / 高度:{{ height }}
</div>
</template>
窗口缩放时,宽高会自动更新,不用在组件里写任何监听逻辑,清爽多了。
用 useTemplateRef 实现复用的小技巧
其实核心就 3 个点,记住就能灵活封装:
- Hook 内部用字符串固定 ref 标识,比如 'auto-focus'、'resize-el',不用暴露变量;
- 组件模板里给目标元素加对应的 ref="标识名",不用管 Hook 内部逻辑;
- 所有 DOM 操作、事件监听都写在 Hook 里,组件只负责引入和使用结果,零侵入。
这样封装出来的 Hook 才是真正可复用的 ------ 不管哪个组件用,都不用改 Hook 代码,也不用在组件里写额外逻辑。
最后总结下使用感受
useTemplateRef 最让我惊喜的是「彻底解耦」:之前用传统 ref 封装的 Hook,组件和 Hook 之间还得通过变量名关联,现在完全不用管这些,Hook 负责处理逻辑,组件负责展示,边界特别清晰。
而且它是 Vue3.5+ 官方支持的写法,TypeScript 类型推断也很友好,不用手动声明类型。现在我把项目里所有操作 DOM 的逻辑都用这种方式封装了,比如滚动监听、点击 outside 关闭、图片懒加载,一次封装全项目复用,效率提升太多了。
如果你的项目还在 Vue3.5 以上,强烈试试这个 API,能少写很多重复代码~