Vue 3.5 新功能学习笔记
这份笔记根据 Vue 官方团队成员的分享,整理了 Vue 3.5 版本带来的主要新功能和优化,旨在帮助开发者快速理解和应用这些新特性。
1. defineProps 支持解构赋值与默认值
这是对开发者日常使用影响较大的一个便利性更新,简化了 props 的默认值定义。
✨ 新语法
现在可以直接在 defineProps 的解构中为属性设置默认值,代码更直观、简洁。
typescript
// 新的写法
const { count, message = 'hello' } = defineProps<{
count: number,
message?: string
}>()🆚 旧语法对比
在此之前,我们需要借助 withDefaults API 来实现,写法相对繁琐。
typescript
// 旧的写法
const props = withDefaults(defineProps<{
count: number,
message?: string
}>(), {
message: 'hello'
})⚠️ 重要注意事项
当使用 watch 监听解构出来的 props 变量时,不能直接监听该变量,因为它失去了响应性。必须将其包装在一个 getter 函数中。
typescript
// 错误用法,会报错或无法正确监听
watch(count, (newVal) => {
console.log('count changed:', newVal)
})
// 正确用法,需要提供一个 getter 函数
watch(() => count, (newVal) => {
console.log('count changed:', newVal)
})2. 新增 Composition API: useId
useId 用于生成一个在客户端和服务端都唯一的 ID,以解决 SSR (服务端渲染) 中的 id 不匹配问题。
✨ 核心功能
useId()返回一个全局唯一的、自增的字符串ID。
⚠️ 重要注意事项:多应用实例场景
useId仅能确保在单个 Vue 应用实例 (createApp) 中是唯一的。- 如果你在同一个页面上创建了多个独立的 Vue 应用,不同应用生成的 ID 可能会重复。
🔧 解决方案
- 为每个应用实例配置一个唯一的
idPrefix,确保最终生成的 ID 不会冲突。
javascript
// app1.js
const app1 = createApp(App)
app1.config.idPrefix = 'app1-'
app1.mount('#app1')
// app2.js
const app2 = createApp(App)
app2.config.idPrefix = 'app2-'
app2.mount('#app2')3. 新增 Composition API: useTemplateRef
这是一个专门用于获取模板中 DOM 元素引用的 API,让意图更加清晰。
✨ 核心功能
useTemplateRef使得获取 DOM 引用的代码与声明响应式状态的ref在语义上分离开来,避免混淆。- 变量名不再需要和模板中的
ref字符串保持一致,更加灵活。
🆚 语法对比
html
<template>
<button ref="button-ref">Click Me</button>
</template>
<script setup lang="ts">
import { ref, useTemplateRef onMounted } from 'vue'
// 新的写法:专门、清晰、变量名解耦
// 可以传入泛型来精确定义元素类型
const myButtonElement = useTemplateRef<HTMLButtonElement>('button-ref')
// 旧的写法:变量名必须与 ref 值一致
const buttonRef = ref<HTMLButtonElement | null>(null) // <button ref="buttonRef">
onMounted(() => {
console.log(myButtonElement.value) // 拿到 button DOM 元素
})
</script>4. watch 副作用清理: onWatchCleanup
watch 和 watchEffect 现在提供了一个更明确的方式来清理上一次执行时产生的副作用(如事件监听、定时器等)。
✨ 核心功能
- 在
watch的回调函数中,可以调用新增的onWatchCleanup函数,或者使用回调函数的第三个参数(也叫onCleanup)。 - 这个清理函数会在下一次
watch回调执行前或者在watch被停止时自动执行。
🔧 使用场景:避免重复注册事件
javascript
import { ref, watch, onUnmounted } from 'vue'
const count = ref(1)
const handleClick = (event) => {
console.log('clicked!', count.value, event)
}
watch(count, (newValue, oldValue, onCleanup) => {
console.log(`count 变为 ${newValue},注册新事件`)
window.addEventListener('click', handleClick)
// 注册一个清理函数,它会在 count 再次变化前执行
onCleanup(() => {
console.log(`清理上一次 (count=${newValue}) 的事件`)
window.removeEventListener('click', handleClick)
})
}, { immediate: true })
// 这个功能等价于使用独立的 onWatchCleanup API
// import { onWatchCleanup } from 'vue'
// onWatchCleanup(() => { ... })5. watch 的 deep 选项支持数字类型
这是一个针对性能优化的“隐藏”功能,允许你控制深度监听的最大层数。
✨ 核心功能
watch的deep选项除了true/false,现在可以接受一个数字。- 这个数字代表了需要递归侦听的最大属性层数。
🚀 解决的问题
当监听一个层级很深、数据量庞大的对象或数组时,deep: true 会递归遍历所有嵌套属性,导致昂贵的性能开销。如果明确知道只有前几层数据会变化,就可以使用 deep: <number> 来避免不必要的深度遍历。
🔧 代码示例
假设我们有一个深度嵌套的数组,但我们只关心数组本身(第一层)和数组内对象的直接属性(第二层)的变化。
javascript
const list = ref([
{
id: 1,
data: {
deep: {
value: 100 // 第四层,不希望被监听
}
},
d: 1 // 第二层,希望被监听
}
])
watch(list, () => {
console.log('Watcher triggered!')
}, {
// 只监听 2 层深度
// 第1层: list.push(), list.pop(), list[0] = ...
// 第2层: list.value[0].d = ...
deep: 2
})
// 这个修改会触发 watch
list.value[0].d++
// 这个修改不会触发 watch,因为深度超过了 2
list.value[0].data.deep.value++6. 其他重要更新
- 响应式系统优化: 对内部的响应式系统进行了重写,显著优化了内存占用和依赖追踪的性能。对于开发者来说,日常使用方式不变,但在处理大规模响应式对象时会有性能提升。
watch的once选项 (Vue 3.4 已加入): 作为一个回顾,watch支持{ once: true }选项,使得监听器在触发一次后自动停止。