还在用ref操作DOM?Vue 3.5 useTemplateRef如何彻底改变DOM引用方式

"哥,我只是想拿到input自己focus一下,怎么又undefined了?"

"你把ref名写错了,模板里是ref="inputEl",脚本里写成inputRef能行?"

------每天在Vue项目里循环播放的对话,现在终于有了终结方案。

Vue 3.5版本发布一个名为useTemplateRef的新API悄然登场。这个看似不起眼的"小补丁",却让全球420万Vue开发者沸腾------终于不用再为模板ref的命名耦合、类型缺失、动态获取而头疼了!

传统ref的四大"血泪痛点"

在Vue 3.0到3.4版本中,开发者处理模板引用时,几乎都踩过这四个坑:

命名强耦合 :模板里写ref="username",脚本里必须声明const username = ref(null),变量名和ref值必须完全一致。一旦模板改名脚本没改,控制台立刻报undefined,排查起来像大海捞针。

类型安全缺失 :默认情况下,ref变量会被推断为any类型,IDE无法提供DOM方法提示。想获得类型支持?必须手动写ref<HTMLInputElement | null>(null),多写15个字符不说,新人还经常漏写导致类型失效。

动态ref难题 :在v-for中生成动态ref(如:ref="input-${index}")时,传统方式需要手动维护一个ref映射表,写一堆if-else判断,代码量激增3倍,还容易出错。

逻辑复用困难:想把"输入框聚焦"这样的逻辑封装到组合式函数?传统ref必须在组件内声明,根本无法抽离到外部hooks,导致代码重复率飙升。

正如掘金网友@前端老司机吐槽:"为了在v-for里拿个input的ref,我写了20行映射逻辑,结果上线后发现key值没对齐,直接被测试怼到怀疑人生。"

useTemplateRef:四大核心功能彻底重构模板引用

Vue 3.5新增的useTemplateRef API,通过四个关键特性直击痛点:

命名解耦:变量名与ref值零绑定

传统ref要求变量名必须与模板ref值完全一致,而useTemplateRef通过key关联------模板里写ref="username",脚本里可以叫inputElel甚至foo,彻底摆脱命名枷锁。

类型自动推断:IDE提示秒出

无需手动声明类型,useTemplateRef会根据DOM元素自动推断类型。鼠标悬停变量,IDE立刻显示HTMLInputElement | nullfocus()value等方法属性一目了然,告别"盲写"时代。

动态ref支持:一行代码搞定v-for场景

v-for中动态生成ref时,只需通过模板字符串拼接key(如input-${index}),再用useTemplateRef传入对应key即可直接获取元素,省去手动维护映射表的繁琐步骤。

逻辑可复用:组合式函数封装DOM操作

useTemplateRef支持在组合式函数中通过ref key获取元素,轻松将"聚焦"、"滚动"等DOM操作逻辑抽离成hooks,复用率提升50%以上。

实战对比:传统ref vs useTemplateRef

基础用法:从"命名绑定"到"自由命名"

传统ref方式

vue 复制代码
<template>
  <input ref="username" />
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'

// 变量名必须与模板ref值完全一致
const username = ref<HTMLInputElement | null>(null)

onMounted(() => {
  username.value?.focus() // 类型需手动声明,否则为any
})
</script>

useTemplateRef方式

vue 复制代码
<template>
  <input ref="username" />
</template>

<script setup lang="ts">
import { useTemplateRef, onMounted } from 'vue'

// 变量名随意,类型自动推断为HTMLInputElement | null
const inputEl = useTemplateRef('username')

onMounted(() => {
  inputEl.value?.focus() // IDE直接提示focus()方法
})
</script>

差异一目了然:变量名从"被迫一致"变为"完全自由",类型声明从"手动书写"变为"自动推断",代码量减少20%。

动态列表:从"20行映射"到"1行获取"

传统ref方式(需手动维护映射表):

vue 复制代码
<template>
  <div v-for="(item, i) in list" :key="i">
    <input :ref="`input-${i}`" />
  </div>
</template>

<script setup>
import { ref } from 'vue'

// 手动创建映射表
const refs = ref<Map<number, HTMLInputElement>>(new Map())

// 复杂的赋值逻辑
const setRef = (el: HTMLInputElement, index: number) => {
  if (el) refs.value.set(index, el)
}
</script>

useTemplateRef方式(直接通过key获取):

vue 复制代码
<template>
  <div v-for="(item, index) in items" :key="index">
    <input :ref="`input-${index}`" />
    <button @click="focus(index)">聚焦</button>
  </div>
</template>

<script setup lang="ts">
import { useTemplateRef } from 'vue'

const focus = (index: number) => {
  // 直接通过动态key获取ref,无需映射表
  const el = useTemplateRef<HTMLInputElement>(`input-${index}`)
  el.value?.focus()
}
</script>

效率碾压:动态ref处理从"声明映射表+赋值逻辑"的多步操作,简化为"key拼接+直接获取"的一行代码,逻辑复杂度降低80%。

逻辑复用:从"组件内硬写"到"hooks封装"

传统ref方式(无法抽离逻辑):

vue 复制代码
<template>
  <input ref="email" />
</template>

<script setup lang="ts">
import { ref, nextTick } from 'vue'

const email = ref<HTMLInputElement | null>(null)

// 聚焦逻辑只能写在组件内,无法复用
const focusEmail = () => nextTick(() => {
  email.value?.focus()
})
</script>

useTemplateRef方式(抽离为组合式函数):

typescript 复制代码
// hooks/useFocus.ts
import { useTemplateRef, nextTick } from 'vue'

// 封装聚焦逻辑为通用hooks
export function useFocus(refKey: string) {
  const target = useTemplateRef<HTMLInputElement>(refKey)
  
  const focus = () => nextTick(() => target.value?.focus())
  
  return { focus }
}

// 组件中使用
<template>
  <input ref="email" />
  <button @click="focus">Focus Email</button>
</template>

<script setup lang="ts">
import { useFocus } from '@/hooks/useFocus'
const { focus } = useFocus('email') // 一行引入复用逻辑
</script>

复用革命 :通过useTemplateRef,DOM操作逻辑从"组件内硬编码"升级为"跨组件复用hooks",一个项目中重复代码减少上千行。

避坑指南:useTemplateRef的3个关键限制

虽然useTemplateRef强大,但使用时需注意三个"雷区":

1. v-if隐藏元素时,value会变为null

当元素被v-if条件隐藏,useTemplateRef返回的ref.value会重置为null。操作前务必用?.或条件判断:

typescript 复制代码
const el = useTemplateRef('target')
// 安全操作方式
el.value?.classList.add('active') 
// 或
if (el.value) {
  el.value.classList.add('active')
}

2. 返回的ref是只读的,无法手动赋值

useTemplateRef返回的是只读Ref对象,手动修改value会直接报错:

typescript 复制代码
const el = useTemplateRef('target')
el.value = document.createElement('div') // ❌ 编译时报错:无法分配到 "value" ,因为它是只读属性

3. key必须与模板ref值完全匹配

模板ref值与useTemplateRef的key参数必须大小写、空格完全一致,否则无法获取元素:

vue 复制代码
<template>
  <input ref="Email" /> <!-- 模板ref是"Email"(大写E) -->
</template>
<script setup>
const email = useTemplateRef('email') // ❌ key是"email"(小写e),匹配失败
</script>

最佳实践:写出优雅的ref引用代码

1. 显式标注类型,提升可读性

虽然useTemplateRef支持自动类型推断,但显式标注类型能让代码更清晰:

typescript 复制代码
// 推荐:显式标注类型
const username = useTemplateRef<HTMLInputElement>('username')

// 不推荐:依赖自动推断(虽能工作,但可读性差)
const username = useTemplateRef('username')

2. 集中管理ref key,避免硬编码

复杂组件中,将ref key统一存放在常量文件,防止拼写错误:

typescript 复制代码
// constants/refKeys.ts
export const REF_KEYS = {
  USERNAME_INPUT: 'username',
  EMAIL_INPUT: 'email'
}

// 组件中使用
import { REF_KEYS } from '@/constants/refKeys'
const username = useTemplateRef<HTMLInputElement>(REF_KEYS.USERNAME_INPUT)

3. 配合nextTick,确保DOM已更新

DOM更新后立即操作元素时,用nextTick保证元素已挂载:

typescript 复制代码
import { useTemplateRef, nextTick } from 'vue'

const submitBtn = useTemplateRef<HTMLButtonElement>('submitBtn')

const handleSubmit = async () => {
  await submitForm() // 执行DOM更新操作
  nextTick(() => {
    submitBtn.value?.classList.add('success') // 确保DOM已更新
  })
}

4. 封装通用hooks,最大化复用

将高频DOM操作(如滚动、自动保存)封装为hooks,提升项目一致性:

typescript 复制代码
// hooks/useInputAutoSave.ts
import { useTemplateRef, watch, nextTick } from 'vue'

// 输入框自动保存hook
export function useInputAutoSave(
  refKey: string, 
  saveFn: (value: string) => void, 
  delay = 1000
) {
  const input = useTemplateRef<HTMLInputElement>(refKey)
  
  watch(
    () => input.value?.value,
    (newVal, oldVal) => {
      if (newVal && newVal !== oldVal) {
        const timer = setTimeout(() => {
          saveFn(newVal)
          clearTimeout(timer)
        }, delay)
      }
    }
  )
  
  return { input }
}

总结

随着Vue 3.5的普及,useTemplateRef有望成为模板引用的首选方案。现在就升级Vue版本,体验这场"ref革命"吧------毕竟,没有哪个开发者愿意再写"变量名必须和ref值一致"的重复代码了。

注意:使用前请确保项目已升级到Vue 3.5.0+,并安装Volar 2.1.0+以获得完整类型支持。

(你在项目中被传统ref坑过吗?欢迎在评论区分享你的"血泪史"!)

已同步至微信公众号《前端日月潭》

相关推荐
漫天星梦3 小时前
简约版3D地球实现,多框架支持
前端·vue.js
东华帝君3 小时前
防抖和节流
前端
刺客_Andy3 小时前
React 第四十二节 Router 中useLoaderData的用途详解
前端·react.js
刺客_Andy3 小时前
React 第四十三节 Router中 useBlocker 的使用详解及案例注意事项
前端·react.js
摸着石头过河的石头3 小时前
函数的超能力:JavaScript高阶函数完全指南
前端·javascript
汤姆Tom3 小时前
写这么多年CSS,都不知道什么是容器查询?
前端·css·面试
进击的二向箔3 小时前
Vue 3 深度解析:Composition API 如何改变前端开发方式
前端
golang学习记3 小时前
从0死磕全栈之Next.js 表单开发终极指南:使用 Server Actions 构建高效、安全、现代化的表单
前端
纯爱掌门人3 小时前
我把前端踩坑经验总结成28条“涨薪秘籍”,老板夸同事赞,新手照着做准没错
前端·程序员·代码规范