Vue 3.5 重磅新特性:useTemplateRef 让模板引用更优雅、更高效!

目录

  • 前言
  • 什么是 useTemplateRef
  • 传统 ref 的问题
  • useTemplateRef 的优势
  • 基础用法
  • 进阶用法
  • 最佳实践
  • 迁移指南
  • 性能对比
  • 注意事项
  • 总结

前言

Vue 3.5 带来了一个激动人心的新特性 useTemplateRef,它彻底革新了我们在 Vue 3 中处理模板引用的方式。这个新的 Composition API 不仅让代码更加优雅,还提供了更好的类型安全性和性能优化。

什么是 useTemplateRef

useTemplateRef 是 Vue 3.5 新增的 Composition API,专门用于处理模板引用(template refs)。它提供了一种更直观、更类型安全的方式来访问 DOM 元素和组件实例。

核心特性

  • 🎯 类型安全:完美的 TypeScript 支持
  • 🚀 性能优化:更高效的内部实现
  • 💡 简洁语法:更直观的 API 设计
  • 🔄 响应式:与 Vue 的响应式系统深度集成

传统 ref 的问题

在 Vue 3.5 之前,我们通常这样处理模板引用:

问题示例

<template><div><input ref="inputRef" /><button @click="focusInput">聚焦输入框</button><MyComponent ref="componentRef" /></div>
</template><script setup lang="ts">
import { ref, onMounted } from 'vue'
import MyComponent from './MyComponent.vue'// 问题1:类型推断不够精确
const inputRef = ref<HTMLInputElement>()
const componentRef = ref<InstanceType<typeof MyComponent>>()// 问题2:需要手动类型断言
const focusInput = () => {inputRef.value?.focus() // 需要可选链
}// 问题3:在 onMounted 之前 ref.value 为 undefined
onMounted(() => {console.log(inputRef.value) // 可能为 undefined
})
</script>

存在的问题

  1. 类型推断复杂:需要手动指定泛型类型
  2. 运行时检查:需要使用可选链操作符
  3. 生命周期依赖:只能在特定生命周期后使用
  4. 代码冗余:重复的类型声明和空值检查

useTemplateRef 的优势

1. 类型安全

<script setup lang="ts">
import { useTemplateRef } from 'vue'// 自动类型推断,无需手动指定类型
const inputRef = useTemplateRef<HTMLInputElement>('inputRef')
const buttonRef = useTemplateRef<HTMLButtonElement>('buttonRef')// TypeScript 会自动推断出正确的类型
const focusInput = () => {inputRef.value?.focus() // 完美的类型提示
}
</script>

2. 更好的性能

// 内部优化,减少不必要的响应式开销
const elementRef = useTemplateRef('elementRef')// 自动优化,只在需要时创建响应式引用

3. 简洁的 API

<template><input ref="inputRef" /><button ref="buttonRef" @click="handleClick">点击</button>
</template><script setup lang="ts">
import { useTemplateRef } from 'vue'// 一行代码搞定
const inputRef = useTemplateRef<HTMLInputElement>('inputRef')
const buttonRef = useTemplateRef<HTMLButtonElement>('buttonRef')const handleClick = () => {inputRef.value?.focus()
}
</script>

基础用法

1. DOM 元素引用

<template><div><input ref="usernameInput" placeholder="请输入用户名"@keyup.enter="handleSubmit"/><button ref="submitButton" @click="handleSubmit">提交</button><div ref="messageContainer"></div></div>
</template><script setup lang="ts">
import { useTemplateRef, nextTick } from 'vue'// 创建模板引用
const usernameInput = useTemplateRef<HTMLInputElement>('usernameInput')
const submitButton = useTemplateRef<HTMLButtonElement>('submitButton')
const messageContainer = useTemplateRef<HTMLDivElement>('messageContainer')// 聚焦输入框
const focusUsername = () => {usernameInput.value?.focus()
}// 提交处理
const handleSubmit = async () => {const username = usernameInput.value?.valueif (!username) {await showMessage('请输入用户名', 'error')focusUsername()return}// 禁用按钮if (submitButton.value) {submitButton.value.disabled = true}try {// 模拟 API 调用await submitForm(username)await showMessage('提交成功!', 'success')} catch (error) {await showMessage('提交失败,请重试', 'error')} finally {// 恢复按钮状态if (submitButton.value) {submitButton.value.disabled = false}}
}// 显示消息
const showMessage = async (text: string, type: 'success' | 'error') => {if (!messageContainer.value) returnmessageContainer.value.textContent = textmessageContainer.value.className = `message ${type}`await nextTick()// 3秒后清除消息setTimeout(() => {if (messageContainer.value) {messageContainer.value.textContent = ''messageContainer.value.className = ''}}, 3000)
}// 模拟 API 调用
const submitForm = (username: string): Promise<void> => {return new Promise((resolve, reject) => {setTimeout(() => {Math.random() > 0.3 ? resolve() : reject(new Error('网络错误'))}, 1000)})
}
</script><style scoped>
.message {padding: 8px;margin-top: 10px;border-radius: 4px;
}.message.success {background-color: #d4edda;color: #155724;border: 1px solid #c3e6cb;
}.message.error {background-color: #f8d7da;color: #721c24;border: 1px solid #f5c6cb;
}
</style>

2. 组件实例引用

<template><div><UserProfile ref="userProfileRef" :user-id="currentUserId"/><AdminPanel ref="adminPanelRef" v-if="isAdmin"/><button @click="refreshUserData">刷新用户数据</button><button @click="openAdminSettings" v-if="isAdmin">管理员设置</button></div>
</template><script setup lang="ts">
import { useTemplateRef, ref } from 'vue'
import UserProfile from './components/UserProfile.vue'
import AdminPanel from './components/AdminPanel.vue'// 组件引用
const userProfileRef = useTemplateRef<InstanceType<typeof UserProfile>>('userProfileRef')
const adminPanelRef = useTemplateRef<InstanceType<typeof AdminPanel>>('adminPanelRef')// 数据
const currentUserId = ref(123)
const isAdmin = ref(true)// 刷新用户数据
const refreshUserData = async () => {// 调用子组件的方法await userProfileRef.value?.refreshData()// 获取子组件的数据const userData = userProfileRef.value?.getUserData()console.log('用户数据:', userData)
}// 打开管理员设置
const openAdminSettings = () => {// 调用管理员面板的方法adminPanelRef.value?.openSettings()// 访问管理员面板的状态const isSettingsOpen = adminPanelRef.value?.settingsVisibleconsole.log('设置面板状态:', isSettingsOpen)
}
</script>

3. 动态引用

<template><div><div v-for="(item, index) in items" :key="item.id":ref="el => setItemRef(el, index)"class="item">{{ item.name }}</div><button @click="highlightRandomItem">随机高亮</button></div>
</template><script setup lang="ts">
import { ref, onUpdated } from 'vue'interface Item {id: numbername: string
}const items = ref<Item[]>([{ id: 1, name: '项目 1' },{ id: 2, name: '项目 2' },{ id: 3, name: '项目 3' },{ id: 4, name: '项目 4' },{ id: 5, name: '项目 5' }
])// 动态引用集合
const itemRefs = ref<HTMLDivElement[]>([])// 设置动态引用
const setItemRef = (el: Element | null, index: number) => {if (el && el instanceof HTMLDivElement) {itemRefs.value[index] = el}
}// 清理无效引用
onUpdated(() => {itemRefs.value = itemRefs.value.slice(0, items.value.length)
})// 高亮随机项目
const highlightRandomItem = () => {// 清除之前的高亮itemRefs.value.forEach(el => {if (el) {el.classList.remove('highlight')}})// 随机选择一个项目高亮const randomIndex = Math.floor(Math.random() * items.value.length)const targetElement = itemRefs.value[randomIndex]if (targetElement) {targetElement.classList.add('highlight')targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' })}
}
</script><

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/95447.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/95447.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/95447.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uni app 的app端 写入运行日志到指定文件夹。

uni app 的app 端 写入指定目录文件夹。并自动生成当前日期的日志文件。删除十日前的日志文件其中 writefile.js 代码如下const {default: logger } require("./logger")var name var url var params var method var resfunction setlog(name, url, params, method)…

桌面应用开发语言与框架选择指南

桌面应用开发的语言和框架选择非常丰富&#xff0c;从原生性能到跨平台解决方案应有尽有。下面我将它们分为几大类进行详细介绍&#xff0c;并附上各自的优缺点和适用场景。 一、 原生开发 (Native Development) 原生开发能提供最佳的性能和与操作系统最完美的集成体验。 1. …

C++知识

文章目录1.Cmap为什么线程不安全?2.map大量插入会有性能问题&#xff0c;为什么3.set的应用场景4.map set mutiset mutimap unordered_map unordered_set的底层实现、使用场景、优缺点1.Cmap为什么线程不安全? 其实STL中的容器都是线程不安全的&#xff0c;如果想要线程安全…

自学嵌入式第三十四天:网络编程-TCP

一、UDP用户数据报收发次数要对应&#xff1b;数据与数据之间有边界&#xff0c;多次调用收发时都是不同的数据报&#xff1b;接收方的数据大小>发送方的数据大小&#xff0c;如果接受方数据小了则会丢弃未读的部分&#xff0c;再次调用只会读下一包数据&#xff1b;二、服务…

Apache IoTDB:国产时序数据库的崛起与工业物联网的未来

&#x1f4d1;前言 在工业物联网的浪潮中&#xff0c;数据不再是副产品&#xff0c;而是驱动决策的核心资产。"随着物联网、工业互联网和智能监控的迅猛发展&#xff0c;时序数据正以前所未有的速度爆发。据预测&#xff0c;到2025年全球物联网设备将达750亿台&#xff0c…

一键核验,安全无忧!手机号三要素详情版API,为您的业务筑牢身份认证防线

一、什么是手机号三要素核验API&#xff1f; 手机号三要素核验API 是一种通过编程接口&#xff0c;实时验证一条个人身份信息是否与该国运营商登记的实名信息一致的在线服务。 这里的“三要素”特指&#xff1a; 姓名 身份证号码 手机号码 核验过程&#xff1a;用户提交上述三个…

轻松上手 qData 数据中台开源版:Docker Compose 助你10分钟跑起来

说在前面 谁适合看这份指南&#xff1f; 初次接触 qData&#xff0c;希望快速体验功能的小伙伴不想折腾复杂环境配置和前端打包的人想用“一键启动”省事体验完整平台的用户 我们已经为你准备好“开箱即用”的完整部署包&#xff0c;包括&#xff1a; ✅ 前端静态资源&…

Qt读写Excel--QXlsx基本使用

1、概述 Document 类是一个用于操作 XLSX 文件的类&#xff0c;继承自 QObject。它提供了对 Excel 文件的读写操作&#xff0c;包括单元格的读写、图片和图表的插入、单元格合并、列和行的格式化、数据验证和条件格式化等功能。此外&#xff0c;它还支持对工作簿和工作表的操作…

P13929 [蓝桥杯 2022 省 Java B] 山 题解

缩减一下题目的意思&#xff0c;问区间 [2022,2022222022] 有多少个数是回文数并且先单调不减&#xff0c;后单调不增。 因为有这两条条件&#xff0c;我们可以得知在判断时只用判断前半段的每个数是不是和对面相应的位置相等&#xff0c;以及是否单调不减。 为什么不用看后半段…

Unity Android 文件的读写

配置AndroidManifest 文件在Assets 目录下查找AndroidManifest 文件&#xff0c;添加权限声明&#xff0c;在application 节点中添加requestLegacyExternalStorage 属性。<!-- 权限声明 --> <uses-permission android:name"android.permission.READ_EXTERNAL_STO…

Pydantic模型验证测试:你的API数据真的安全吗?

url: /posts/03b2afdf35f55dbaef631710ab6da82c/ title: Pydantic模型验证测试:你的API数据真的安全吗? date: 2025-09-03T23:46:18+08:00 lastmod: 2025-09-03T23:46:18+08:00 author: cmdragon summary: Pydantic在FastAPI中用于数据验证和序列化,通过Python类型注解自动…

【Proteus仿真】AT89C51单片机中断系列仿真——INT0中断控制LED小灯/INT0和INT1中断控制数码管

目录 0案例视频效果展示 0.1例子1&#xff1a;INT0控制LED闪烁 0.2例子2&#xff1a;INT0中断控制数码管计数 0.3例子3&#xff1a;INT0中断实现秒表功能 0.4例子4&#xff1a;INT0INT1中断控制数码管计数 1基础知识补充——中断系统 1.1 中断源一览 1.2 控制寄存器 1…

MTK Linux DRM分析(三十三)- MTK mtk_mipi_tx.c

一、MIPI PHY驱动简介 1. MIPI 协议分层 应用层:显示(DSI)、摄像头(CSI)。 协议层:定义像素/图像帧如何封装成数据包。 物理层(PHY):具体电气信号传输方式 —— 这里就是 D-PHY 或 C-PHY。 2. D-PHY(Differential PHY) 传输方式:差分信号(类似 LVDS/USB/PCIe …

G2D 图形加速器

文章目录G2D 图形加速器1. 功能简介1.1 矩形填充1.2 旋转和镜像 (rotate and mirror)1.3 透明度混合1.4 colorkey1.5 缩放 (Stretchblt)2. G2D 框架3. 全志 G2D 使用示例3.1 使用G2D实现图像旋转缩放3.2 实时预览中加入旋转缩放功能G2D 图形加速器 G2D模块主要实现图像旋转、数…

【FPGA】单总线——DS18B20

目录 项目&#xff1a;项目&#xff08;含quartus工程、仿真文件&#xff09; 1. 单总线通信时序详解 1.1 初始化&#xff08;复位脉冲 存在脉冲&#xff09; 1.2 写时隙&#xff08;写“0”和写“1”&#xff09; 1.3 读时隙 2. DS18B20 暂存器与温度数据格式 2.1 暂存…

JUC的安全并发包机制

目录 1. Lock机制&#xff1a;明锁控制 2. 栅栏机制(CyclicBarrier) 3. 闭锁机制(CountDownLatch) 4. 信号量机制(Semaphore) 5. 无锁机制 1. Lock机制&#xff1a;明锁控制 Lock接口提供了比synchronized更灵活的锁机制&#xff0c;属于明锁&#xff08;需要手动获取和释…

开源企业级快速开发平台(JeecgBoot)

JeecgBoot 是一款基于 Spring Boot Vue 技术栈的开源企业级快速开发平台&#xff0c;旨在通过「低代码代码生成」模式降低企业级应用的开发成本&#xff0c;提升开发效率。其核心定位是“开箱即用的中后台解决方案”&#xff0c;覆盖权限管理、表单报表、工作流、代码生成等核…

探索 PostgreSQL 和 MySQL 之间的主要差异和相似之处,找到满足您项目需求的最佳数据库解决方案。

探索 PostgreSQL 和 MySQL 之间的主要差异和相似之处&#xff0c;找到满足您项目需求的最佳数据库解决方案。 探索 PostgreSQL 和 MySQL 之间的主要差异和相似之处&#xff0c;找到满足您项目需求的最佳数据库解决方案。 关系数据库已经存在了很长时间。事实上&#xff0c;关系…

如何画时序图、流程图、状态流转图

如何画时序图、流程图、状态流转图流程图符号约定时序图元素交互框最佳实践状态流转图在研发或者写技术方案的时候&#xff0c;我们经常会画各种图。图比文字更加容易理解一些&#xff0c;那么如何画出优秀好看的图呢下面简单介绍一些画图时需要注意的点 流程图 流程图是流程…

CSDN 与 掘金 高效学习指南

CSDN 和掘金&#xff08;juejin.cn&#xff09;是国内最活跃的技术社区&#xff0c;但信息量巨大、质量参差不齐。高效运用的关键是&#xff1a;从“被动浏览”转向“主动获取”&#xff0c;避免陷入“收藏一堆文章却学不会”的陷阱。 以下是为你量身定制的CSDN 与 掘金 高效学…