当前位置: 首页 > news >正文

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

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.dtcms.com/a/364000.html

相关文章:

  • 服务器托管需要注意什么事项?
  • 人工智能助力流感疫苗选择:MIT 团队推出 VaxSeer 系统
  • MySQL注意事项与规范
  • 开发AI编程工具的方案分析
  • SPI片选踩坑实录(硬件片选和软件片选)
  • Nacos配置文件攻防思路总结|揭秘Nacos被低估的攻击面|挖洞技巧
  • Python 基础核心概念与实战代码示例(含数据类型、变量、流程控制、数据结构、函数与文件操作)
  • # Shell 文本处理三剑客:awk、sed 与常用小工具详解
  • 如何修改 Docker 默认网段(网络地址池)配置:以使用 10.x.x.x 网段为例
  • 2024 年 AI 产业格局复盘:头部企业竞逐方向与中小玩家生存破局点
  • 跨境电商账号风控核心:IP纯净度与浏览器指纹的防护策略
  • 基于单片机车流车速检测系统设计
  • 90%的C++ 程序员都忽略了这个容器——unordered_multiset,让我们来看看开源项目中怎么使用的
  • 最小二乘法之线性回归篇(普通最小二乘OLS、加权最小二乘WLS、广义最小二乘GLS)-原理讲解
  • 毕业项目推荐:69-基于yolov8/yolov5/yolo11的轴承缺陷检测识别系统(Python+卷积神经网络)
  • Python入门教程之类型转换
  • 【 HarmonyOS 6 】HarmonyOS智能体开发实战:Function组件和智能体创建
  • 博客系统的测试
  • Shell脚本一键监控平台到期时间并钉钉告警推送指定人
  • 黑马头条面试重点业务
  • 如何避免研发文档命名混乱导致难以检索
  • 我们正在成为机械半类人你信吗?
  • Photoshop - Ps 处理图层
  • 数字社会学必读书目推荐!唐兴通20年数字社会学探索思想之旅再回顾人工智能社会学AI社会学下新秩序
  • 计算机保研机试准备——C++算法题(二)
  • 嵌入式学习 day62 SPI子系统、adxl345驱动、驱动回顾
  • 依托深兰科技AI技术生态,深兰教育携手沪上高校企业启动就业科创营
  • CRM数据暴风升级!3步将DataEase可视化神技嵌入Cordys,销售分析直接开挂!
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘flake8’问题
  • 【Vue2 ✨】Vue2 入门之旅(十):Vuex 入门