提前解锁 Vue 3.5 的新特性
Vue 3.5 是 Vue.js 新发布的版本,虽然没有引入重大变更,但带来了许多实用的增强功能、内部优化和性能改进。
1. 响应式系统优化
Vue 3.5 进一步优化了响应式系统的性能,并且减少内存占用。尤其在处理大型或深度嵌套的响应式数组时,性能提高了 10 倍。
举个 🌰
<template>
<div>
<p v-for="item in deepArray" :key="item.value">{{ item.value }}</p>
</div>
</template>
<script setup>
import { reactive } from 'vue'
// 创建一个大型的深度嵌套数组,测试响应式系统的优化效果
const deepArray = reactive([...Array(10000)].map(() => reactive({ value: Math.random() })))
</script>
在这个示例中,我们创建了一个包含 10000 个深度嵌套对象的数组。Vue 3.5 对这种场景的内存和性能进行了优化,可以在大量操作和变化时观察到更流畅的响应式处理。
2. 响应式 Props 解构
在 Vue 3.5 中,响应式 props 解构默认启用。在使用 defineProps 时,可以像处理普通 JavaScript 对象那样解构 props,并且解构后的变量保持响应式。
之前的方式:
const props = withDefaults(
defineProps<{ count?: number; msg?: string }>(),
{ count: 0, msg: 'hello' }
)
新方式:
const { count = 0, msg = 'hello' } = defineProps<{ count?: number; msg?: string }>()
解构后的变量 count 和 msg 在模板中使用时会自动保持响应式,且不需要显式地调用 toRefs。
但我没有成功在 Vue 3.5 的项目下运行成功,还需要研究一下 o(╥﹏╥)o。
3. 服务器渲染(SSR)改进
3.1 懒加载补水(Lazy Hydration)
Vue 3.5 引入了懒加载补水功能,使用 defineAsyncComponent() 可以控制异步组件的水合时机。例如:允许异步组件在首次可见时才进行补水操作,减少初次渲染的资源消耗。
3.1.1 解释一下补水(水合操作)!!
补水(Hydration)是一个与服务器端渲染(SSR)相关的术语。
在 SSR 中,Vue 会在服务器上预先渲染组件的 HTML,并将其发送到浏览器。当页面加载时,客户端的 JavaScript 代码会接管这些已经存在的 HTML 元素,并将它们变为动态响应式,这个过程就被称为水合操作(Hydration)。
补水(水合操作)的意义
水合操作的主要目的是将服务器端预渲染的静态 HTML 与客户端的动态 JavaScript 逻辑连接起来,使页面在首次加载时可以快速显示内容,同时在客户端加载完 JavaScript 之后,页面可以正常交互。这种做法提高了页面的首次加载速度和用户体验,特别是在网络环境较差或页面较为复杂时。
补水与懒加载补水
1、普通补水
页面加载时,所有的静态 HTML 会立即被客户端 JavaScript "接管"。这种做法可能会导致在页面初次渲染时,客户端需要同时处理大量的水合任务,从而影响性能。
2、懒加载补水
Vue 3.5 引入的懒加载补水功能指,只在组件首次出现在视口中时才进行水合操作。这样可以减少页面初次渲染时的性能开销,只在需要时才补水,特别适合异步加载的组件或在页面滚动到可见区域时才需要的内容。
举个 🌰
假设我们有一个异步加载的组件 AsyncComponent.vue,在普通补水情况下,这个组件在 SSR 中生成的 HTML 会在页面加载时立即被水合,无论用户是否滚动到该组件的可见区域。而在懒加载补水的情况下,只有当用户滚动到组件可见时,才会触发水合。
App.vue
<template>
<div class="container">
<AsyncComp v-if="visible" />
<button @click="toggleVisibility">Toggle Component</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { defineAsyncComponent, hydrateOnVisible } from 'vue'
const visible = ref(false)
const toggleVisibility = () => {
visible.value = !visible.value
}
const AsyncComp = defineAsyncComponent({
loader: () => import('./components/AsyncComponent.vue'), // 模拟异步加载的组件
hydrate: hydrateOnVisible() // 只有在组件可见时才进行水合
})
</script>
AsyncComponent.vue
<template>
<div>
异步加载的组件
</div>
</template>
展示为:
3.2 useId( ) API
useId( ) 是一个新 API,专门为生成在服务器和客户端渲染过程中保持稳定的唯一 ID。这对于生成表单元素和可访问性属性的 ID 非常有用。
举个 🌰
<template>
<form>
<label :for="id">Name:</label>
<input :id="id" type="text" />
</form>
</template>
<script setup>
import { useId } from 'vue'
// 使用 useId 生成一个唯一的 id
const id = useId()
console.log("~ id:", id)
</script>
此功能确保生成的 ID 在 SSR 和客户端渲染时保持一致,确保表单和可访问性属性不会因不匹配导致警告或错误。
4. 自定义元素改进
Vue 3.5 对 defineCustomElement() API 进行了增强,使 Vue 创建 Web Components 更加灵活。
举个 🌰 App.vue
<template>
<div class="container">
<!-- Vue 自定义元素可以像普通 HTML 元素一样使用 -->
<my-element title="使用自定义元素" description="这是通过 Vue 定义的 Web Component。"></my-element>
<br />
<my-element />
</div>
</template>
<script setup>
import { defineCustomElement } from 'vue'
import MyElement from './components/MyElement.ce.vue'
// 使用 defineCustomElement 注册组件
const MyElementCustom = defineCustomElement(MyElement, {
shadowRoot: false,
})
// 通过 customElements.define 注册为自定义元素
customElements.define('my-element', MyElementCustom)
</script>
MyElement.ce.vue
<template>
<div>
<h1>{{ title }}</h1>
<p>{{ description }}</p>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
default: '默认标题'
},
description: {
type: String,
default: '这是自定义元素的描述内容'
}
}
}
</script>
展示为:
4.1 解释一下 .ce.vue
在 Vue.js 中,.ce.vue 文件名中的.ce 通常表示自定义元素(Custom Element)。这种命名方式并不是 Vue 官方强制要求的,而是一种约定俗成的命名规范,用于区分普通的 Vue 组件和用来创建Web Components的自定义元素组件。
自定义元素在许多场景中很有用,特别是希望在多个框架之间共享组件,或者希望组件能够独立运行时,Web Components 是一个很好的选择。而 .ce.vue 文件命名则帮助开发者清楚地知道这个 Vue 组件是为生成 Web Components 而设计的。
5. 新增 useTemplateRef( )
Vue 3.5 引入了 useTemplateRef() API,它允许动态地获取模板引用,特别适用于引用 ID 动态变化或条件变化的场景。相较于传统的 ref,useTemplateRef 可以在运行时根据不同的条件动态更新引用,而不是依赖于静态的 ref 属性。
举个 🌰
<template>
<input ref="inputRef" type="text" />
</template>
<script setup>
import { useTemplateRef } from 'vue'
// 获取动态引用的 input
const inputRef = useTemplateRef('inputRef')
// 在生命周期中可以访问这个引用
onMounted(() => {
inputRef.value.focus()
})
</script>
6. 延迟传送(defer Teleport)
Vue 内置 <Teleport> 组件在传送内容时,要求目标元素在组件挂载时已经存在。Vue 3.5 引入了 defer 属性,许传送内容到后才渲染的目标元素。
举个 🌰
<template>
<div class="container">
<Teleport defer to="#dynamic-target">
<p>传送内容...</p>
</Teleport>
<div id="dynamic-target"></div>
</div>
</template>
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
setTimeout(() => {
// 模拟目标元素动态渲染
document.getElementById('dynamic-target').innerHTML = '<div>目标元素已渲染</div>'
}, 1000)
})
</script>
展示为:
这里 Teleport 组件会等待目标元素(#dynamic-target)渲染后再将内容传送过去,可以解决传送目标先于组件渲染的问题。
7. 新增 onWatcherCleanUp()
Vue 3.5 引入了 onWatcherCleanup() API,用于在清理 watch 时注册回调函数。例如,可以在 watch 的回调中清理过时的网络请求。
举个 🌰
<template>
<div>
<button @click="id++">更改 ID</button>
<p>当前 ID: {{ id }}</p>
</div>
</template>
<script setup>
import { ref, watch, onWatcherCleanup } from 'vue';
const id = ref(1);
// 监控 id 的变化,并在 watcher 停止时清理过时的网络请求
watch(id, (newId) => {
const controller = new AbortController();
// 发起网络请求
fetch(`/api/data/${newId}`, { signal: controller.signal })
.then((response) => {
if (!response.ok) {
throw new Error(`网络请求失败: ${response.status}`);
}
return response.json(); // 解析 JSON
})
.then((data) => {
console.log('获取的数据:', data);
})
.catch((error) => {
if (error.name === 'AbortError') {
console.log('请求被取消');
} else {
console.error('发生错误:', error);
}
});
// 注册清理函数,取消旧的请求
onWatcherCleanup(() => {
controller.abort();
});
});
</script>
此功能允许在 watch 停止追踪时自动执行清理操作,避免资源泄漏。
8. 总结
Vue 3.5 版本提供了多项增强功能,包括响应式系统的性能优化、响应式 props 解构、SSR 改进、自定义元素支持的扩展等,优化内存、提升性能的同时也提升了 Vue 的开发体验。
有些内容可能不是很详细,大家感兴趣的话,可以自行研究一下。
在此之前:需要确保项目使用的是 Vue 3.5。可以使用以下命令更新项目中的 Vue 版本:
npm install vue@latest
# or
yarn add vue@latest
然后,检查项目中的 package.json,确认 Vue 版本已经更新到 3.5 或更高: