Code前端首页关于Code前端联系我们

1.先搞懂,useTemplateRefs 到底是什么?

terry 18小时前 阅读数 18 #Vue
文章标签 useTemplateRefs;Vue

在Vue3开发里,不少同学遇到过这种麻烦事:列表循环渲染时,每个项都需要操作DOM或者组件实例,得一个个给ref,再手动存到数组里;表单里多个输入框要统一验证,ref变量声明了一大串……要是能“一键收集”所有同类型的ref,代码能少写一半!Vue3.3+推出的useTemplateRefs就是干这个的,但很多人对它的用法、场景还是雾里看花,今天用问答形式,把useTemplateRefs从基础到实战掰碎了讲,帮你在项目里高效用起来~

简单说,useTemplateRefs是Vue3.3+新增的**组合式API**,专门用来批量收集模板里“同名ref”的实例,比如你在v-for循环里给每个元素都绑了`ref="itemRef"`,用useTemplateRefs就能一次性把这些ref全捞出来,不用手动push到数组里。

它的用法很直观:在` ```

对比普通模板ref(单个声明`const refName = ref()`,模板绑`ref="refName"`),useTemplateRefs解决的是**“批量收集同名ref”**的痛点——不用再为每个循环项手动维护数组,Vue帮你自动收集。

和普通模板ref比,useTemplateRefs 优势在哪?

普通模板ref处理“多个同类型元素”时,代码又繁琐又容易出错,举个🌰:做一个hover时显示操作按钮的列表,每个项要绑ref来控制按钮显示。

普通ref写法(痛点明显):

<script setup>
import { ref, onMounted } from 'vue'
const itemRefs = ref([]) // 手动声明数组存ref
onMounted(() => {
  // 这里得确保itemRefs.value长度和列表一致,否则可能漏或多
  console.log(itemRefs.value) 
})
</script>
<template>
  <div 
    v-for="(item, idx) in list" 
    :key="idx" 
    :ref="(el) => itemRefs.value[idx] = el" 
  >
    {{ item }}
  </div>
</template>

这里得手动写回调把每个el塞到数组对应位置,一旦列表增删,还得操心数组长度是否同步,很容易漏判;而且代码里“手动维护数组”的逻辑很冗余。

useTemplateRefs写法(清爽太多):

<script setup>
import { useTemplateRefs, onMounted } from 'vue'
const refs = useTemplateRefs()
onMounted(() => {
  const allItems = refs().itemRef // 直接拿到所有项的ref数组
  allItems.forEach(el => {
    // 批量给每个项加hover事件(示例逻辑)
    el.addEventListener('mouseenter', () => { ... })
  })
})
</script>
<template>
  <div v-for="item in list" :key="item.id" ref="itemRef">
    {{ item.name }}
  </div>
</template>

能看出区别吧?useTemplateRefs帮你自动收集所有`ref="itemRef"`的元素,不用手动处理数组索引、长度,代码量少了一半,维护性直接拉满,特别是列表很长、ref操作逻辑复杂时,这种优势更明显。

实际开发中,哪些场景非用它不可?

不是所有场景都需要useTemplateRefs,但碰到这几类需求,用它能省大功夫:

场景1:列表项的交互/动画管理

todo 列表,每个项hover显示删除按钮、点击展开详情、滑动时触发动画……这些都需要操作每个项的DOM,用useTemplateRefs收集所有项的ref后,能批量绑定事件、修改样式。

再比如“无限滚动列表”,需要给每个新渲染的项加懒加载指令(比如图片懒加载),用useTemplateRefs收集后,一次性给所有项的img绑自定义指令,比逐个处理高效多了。

场景2:表单的批量验证/操作

做登录注册表单时,多个输入框(用户名、密码、验证码)需要在提交前统一验证是否为空、格式是否正确,用useTemplateRefs把所有输入框的ref收集到一个对象里,提交时循环验证,不用每个输入框声明单独的ref变量。

```vue ```

场景3:动态组件的实例管理

用`v-for`渲染多个动态组件(``)时,需要获取每个组件的实例来调用方法,用useTemplateRefs给每个动态组件绑同一个ref,调用refs()就能拿到所有组件实例,批量调用`compInstance.method()`。

场景4:自定义指令的批量绑定

如果有自定义指令(v-permission`控制权限显示),需要给多个元素批量绑指令,useTemplateRefs收集ref后,能一次性遍历所有元素,动态添加/移除指令逻辑,比在模板里重复写指令更灵活。

代码怎么写?一步步教你用

useTemplateRefs的核心是“**声明收集函数 → 模板绑定同名ref → 调用函数获取所有实例**”,下面分步骤拆解:

步骤1:导入并声明收集函数

<script setup>里导入useTemplateRefs,然后声明变量接收返回值(这个变量是函数):

<script setup>
import { useTemplateRefs, onMounted } from 'vue'
const refs = useTemplateRefs() // 关键:声明收集函数
</script>

步骤2:模板中绑定同名ref

在需要收集的元素上,统一用ref="自定义名称",比如v-for循环里的项:

<template>
  <div 
    v-for="(item, idx) in list" 
    :key="idx" 
    ref="cardRef" <!-- 所有项都绑同一个ref名 -->
  >
    {{ item.title }}
  </div>
</template>

步骤3:调用函数,获取所有ref实例

useTemplateRefs返回的函数(比如上面的refs),调用后会返回一个对象,键是ref名称,值是对应所有元素的ref数组,注意:要在DOM渲染完成后调用(比如onMounted、nextTick里),否则可能拿不到最新实例。

<script setup>
import { useTemplateRefs, onMounted, nextTick } from 'vue'
const refs = useTemplateRefs()
const list = ref([/* 数据 */])
onMounted(async () => {
  await nextTick() // 确保DOM更新完毕
  const allCards = refs().cardRef // 拿到所有绑了cardRef的div实例
  allCards.forEach(card => {
    card.style.backgroundColor = '#f5f5f5' // 批量改样式
  })
})
</script>

步骤4:后续操作(事件绑定、逻辑处理)

拿到ref数组后,想干啥就干啥:批量加事件、改样式、调用组件方法……比如给每个卡片加点击事件:

allCards.forEach((card, index) => {
  card.addEventListener('click', () => {
    console.log(`第${index+1}个卡片被点击了`)
  })
})

这些“坑”踩过才知道!使用时要避开什么?

useTemplateRefs好用,但不注意细节容易踩坑,提前避坑能少熬夜debug:

坑1:ref命名冲突,不同列表混在一起

如果页面上有两个v-for列表,都用了`ref="itemRef"`,那refs().itemRef会把两个列表的项全装进去,导致逻辑混乱。

**解决:** 给不同列表的ref取独特名称,ref="listAItem"`和`ref="listBItem"`,这样调用时`refs().listAItem`和`refs().listBItem`就互不干扰。

坑2:调用时机不对,拿不到最新ref

如果在数据更新后立刻调用refs(),但DOM还没重新渲染,拿到的还是旧数据的ref,比如列表新增了一项,直接调用refs(),新项的ref还没被收集。

**解决:** 用`nextTick`确保DOM更新后再调用。 ```vue const addItem = () => { list.value.push(newItem) nextTick(() => { const newRefs = refs().itemRef // 此时newRefs包含新增项的ref }) } ```

坑3:TS类型推导不友好(TypeScript项目)

默认情况下,refs()返回的对象类型是`Record`,TS不知道ref数组里元素的类型(是DOM元素?还是组件实例?)。

**解决:** 手动断言类型,比如知道ref绑定的是`
`,可以这样写: ```typescript const refs = useTemplateRefs() onMounted(() => { const allCards = refs().cardRef as HTMLDivElement[] allCards.forEach(card => { card.style.color = 'red' // TS能正确推导类型 }) }) ``` 如果是组件实例,比如子组件``,可以导入组件类型: ```typescript import MyCard from './MyCard.vue' const refs = useTemplateRefs() onMounted(() => { const cardInstances = refs().cardRef as InstanceType[] cardInstances.forEach(instance => { instance.doSomething() // 调用子组件方法,TS不报错 }) }) ```

拿真实案例对比,看它如何简化开发

光说不练假把式,用“列表项hover显示操作按钮”这个常见需求,对比普通ref和useTemplateRefs的写法,感受代码差异。

需求:todo列表,每个项hover时显示“删除”按钮,离开时隐藏。

普通ref写法(代码冗余):

<template>
  <div 
    v-for="(todo, idx) in todos" 
    :key="idx" 
    :ref="(el) => todoRefs[idx] = el" 
    @mouseenter="showBtn(idx)" 
    @mouseleave="hideBtn(idx)" 
  >
    {{ todo.name }}
    <button v-show="btnVisible[idx]">删除</button>
  </div>
</template>
<script setup>
import { ref } from 'vue'
const todos = ref([/* 数据 */])
const todoRefs = ref([]) // 存每个项的ref
const btnVisible = ref([]) // 存每个按钮的显示状态
const showBtn = (idx) => {
  btnVisible.value[idx] = true
  // 假设还要操作DOM,比如改项的背景色
  todoRefs.value[idx].style.backgroundColor = '#eee'
}
const hideBtn = (idx) => {
  btnVisible.value[idx] = false
  todoRefs.value[idx].style.backgroundColor = ''
}
</script>

这里要维护`todoRefs`数组和`btnVisible`数组,还要在ref回调里手动赋值,逻辑分散,列表长度变化时容易出bug。

useTemplateRefs写法(简洁高效):

<template>
  <div 
    v-for="todo in todos" 
    :key="todo.id" 
    ref="todoItem" 
    @mouseenter="handleEnter" 
    @mouseleave="handleLeave" 
  >
    {{ todo.name }}
    <button v-show="todo.showBtn">删除</button>
  </div>
</template>
<script setup>
import { useTemplateRefs, ref, nextTick } from 'vue'
const todos = ref([/* 数据 */])
const refs = useTemplateRefs()
// 批量处理hover逻辑
const handleEnter = (e) => {
  // 先确保refs已收集最新DOM
  nextTick(() => {
    const allItems = refs().todoItem
    allItems.forEach((item, idx) => {
      if (item === e.target) { // 找到当前hover的项
        todos.value[idx].showBtn = true
        item.style.backgroundColor = '#eee'
      }
    })
  })
}
const handleLeave = (e) => {
  nextTick(() => {
    const allItems = refs().todoItem
    allItems.forEach((item, idx) => {
      if (item === e.target) {
        todos.value[idx].showBtn = false
        item.style.backgroundColor = ''
      }
    })
  })
}
</script>

能看到:useTemplateRefs把“维护ref数组”的逻辑全交给Vue,代码里不用手动处理数组索引,只需要关注“哪个项触发了事件”,逻辑更集中,后续加功能(比如批量改所有项样式)也更方便。

原理层面:Vue是怎么实现批量收集ref的?

想深入理解useTemplateRefs,得简单扒一扒Vue的模板编译和ref收集机制。

Vue在编译模板时,会把`ref`属性处理成“给元素/组件绑定ref实例”的逻辑,对于普通ref(单个元素),Vue会生成一个唯一的key,把ref实例存在组件实例的`refs`对象里,而useTemplateRefs的核心,是**让多个同名ref共享同一个收集逻辑**——当模板中有多个`ref="xxx"`时,Vue会把这些ref实例按顺序存到一个数组里,最终通过useTemplateRefs返回的函数,把这个数组暴露给开发者。

换句话说,useTemplateRefs是Vue对“同名ref批量收集”这个场景的官方封装,底层利用了Vue的响应式系统和模板编译时对ref的处理逻辑,帮我们省去了手动管理ref数组的麻烦。

useTemplateRefs是Vue3.3+给开发者的“批量处理模板ref”的利器,在列表渲染、表单管理、动态组件等场景下能大幅简化代码,核心要记住:它帮你自动收集同名ref,调用函数拿数组,注意命名唯一性和调用时机,下次碰到需要批量操作ref的场景,别再手动push数组了,试试useTemplateRefs,效率直接起飞~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门