useTemplateRef 详解

最近升级 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 个点,记住就能灵活封装:

  1. Hook 内部用字符串固定 ref 标识,比如 'auto-focus'、'resize-el',不用暴露变量;
  1. 组件模板里给目标元素加对应的 ref="标识名",不用管 Hook 内部逻辑;
  1. 所有 DOM 操作、事件监听都写在 Hook 里,组件只负责引入和使用结果,零侵入。

这样封装出来的 Hook 才是真正可复用的 ------ 不管哪个组件用,都不用改 Hook 代码,也不用在组件里写额外逻辑。

最后总结下使用感受

useTemplateRef 最让我惊喜的是「彻底解耦」:之前用传统 ref 封装的 Hook,组件和 Hook 之间还得通过变量名关联,现在完全不用管这些,Hook 负责处理逻辑,组件负责展示,边界特别清晰。

而且它是 Vue3.5+ 官方支持的写法,TypeScript 类型推断也很友好,不用手动声明类型。现在我把项目里所有操作 DOM 的逻辑都用这种方式封装了,比如滚动监听、点击 outside 关闭、图片懒加载,一次封装全项目复用,效率提升太多了。

如果你的项目还在 Vue3.5 以上,强烈试试这个 API,能少写很多重复代码~

相关推荐
卸任4 小时前
Vibe coding工具使用心得
前端·ai编程
brien95 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance5 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M5 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写5 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李5 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易6 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马6 小时前
前沿视界
前端
kyriewen6 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易6 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员