今天我们很高兴地宣布发布 Vue 3.5 "Tengen Toppa Gurren Lagann"!
这个小版本更新没有引入任何破坏性更改,并且包含了内部改进和一些实用的新功能。我们将在本文中介绍一些亮点
响应式系统优化
在 3.5 版本中,Vue 的反应式系统经历了重大重构,实现了更好的性能并显著减少了内存使用(-56%),而且没有改变任何行为。此次重构还解决了在服务端渲染(SSR)过程中由于挂起的计算属性导致的陈旧计算值和内存问题。
此外,3.5 还优化了对大型、深度响应数组的跟踪,在某些情况下使这类操作的速度提升了高达 10 倍
响应式 Props 解构
在 3.5 版本中,响应式 Props 解构功能已经稳定,并默认启用。现在,在 <script setup> 中从 defineProps 调用解构的变量将具有响应式。值得注意的是,此功能通过利用 JavaScript 的原生默认值语法,大大简化了声明具有默认值的 Props:
之前
const props = withDefaults(
defineProps<{
count?: number
msg?: string
}>(),
{
count: 0,
msg: 'hello'
}
)
以后
const { count = 0, msg = 'hello' } = defineProps<{
count?: number
message?: string
}>()
访问解构变量
访问解构后的变量(例如 count)会被编译器自动转换为 props.count,因此它们在访问时会被跟踪。类似于 props.count,如果需要观察解构后的 Props 变量或将它传递给一个组合式函数(composable)以保留其响应性,则需要将其包裹在一个 getter 中。
以下是一个示例:
watch(count /* ... */)
// ^ results in compile-time error
watch(() => count /* ... */)
// ^ wrap in a getter, works as expected
// composables should normalize the input with `toValue()`
useDynamicCount(() => count)
对于希望更好地区分解构后的 Props 和普通变量的开发者,@vue/language-tools 2.1 提供了一个可选设置,用于启用内嵌提示(inlay hints):
SSR 改进
3.5 版本带来了一些长期被请求的服务器端渲染(SSR)改进。
Lazy Hydration
异步组件现在可以通过 defineAsyncComponent() API 的 hydrate 选项来控制何时进行水合(水合是指将服务器端渲染(SSR)生成的静态 HTML 内容转换为动态的、可交互的 Web 组件的过程)。例如,仅在组件变为可见时才进行水合:
import { defineAsyncComponent, hydrateOnVisible } from 'vue'
const AsyncComp = defineAsyncComponent({
loader: () => import('./Comp.vue'),
hydrate: hydrateOnVisible()
})
核心 API 故意设计得较为底层,Nuxt 团队已经在这一特性基础上构建了更高层次的语法糖。
useId()
useId() 是一个可以用来生成在整个应用中唯一的 ID 的 API,这些 ID 在服务器端和客户端渲染时保证是稳定的。它可以用于生成表单元素和无障碍属性的 ID,并且可以在 SSR 应用中使用而不会导致水合不匹配:
<script setup>
import { useId } from 'vue'
const id = useId()
</script>
<template>
<form>
<label :for="id">Name:</label>
<input :id="id" type="text" />
</form>
</template>
data-allow-mismatch
在某些情况下,如果客户端的值不可避免地与服务器端的值不同(例如日期),我们现在可以通过使用 data-allow-mismatch 属性来抑制由此产生的水合不匹配警告:
<span data-allow-mismatch>{{ data.toLocaleString() }}</span>
你还可以通过为该属性提供一个值来限制允许的不匹配类型,可能的值包括 text、children、class、style 和 attribute。
Custom Elements Improvements
3.5 修复了许多与 defineCustomElement() API 相关的长期问题,并添加了多项新的功能,以便使用 Vue 编写自定义元素:
- 通过
configureApp选项支持自定义元素的应用配置。 - 添加
useHost()、useShadowRoot()和this.$hostAPI 以访问自定义元素的宿主元素和影子根(shadow root)。 - 支持通过传递
shadowRoot: false来挂载没有 Shadow DOM 的自定义元素。 - 支持提供
nonce选项,该选项将附加到由自定义元素注入的<style>标签上。
这些新的仅适用于自定义元素的选项可以通过 defineCustomElement 的第二个参数传递:
import MyElement from './MyElement.ce.vue'
defineCustomElements(MyElement, {
shadowRoot: false,
nonce: 'xxx',
configureApp(app) {
app.config.errorHandler = ...
}
})
其他值得注意的功能
useTemplateRef()
3.5 引入了一种新的获取模板引用(Template Refs)的方式,通过 useTemplateRef() API 实现:
<script setup>
import { useTemplateRef } from 'vue'
const inputRef = useTemplateRef('input')
</script>
<template>
<input ref="input">
</template>
在 3.5 之前,我们建议使用普通的 refs,并且变量名称与静态 ref 属性相匹配。旧方法要求 ref 属性能够被编译器分析,因此仅限于静态 ref 属性。相比之下,useTemplateRef() 通过运行时字符串 ID 匹配 refs,因此支持动态绑定到变化的 ID。
@vue/language-tools 2.1 也实现了对新语法的特殊支持,因此在使用 useTemplateRef() 时,你会根据模板中是否存在 ref 属性获得自动补全和警告:
Deferred Teleport(延迟传送)
内置的 <Teleport> 组件的一个已知限制是,其目标元素必须在传送组件挂载时存在。这阻止了用户将内容传送到由 Vue 渲染的其他元素上,如果这些元素是在传送组件之后渲染的。
在 3.5 版本中,我们为 <Teleport> 添加了一个 defer 属性,使其在当前渲染周期之后挂载,因此现在这种方式可以工作了:
<Teleport defer target="#container">...</Teleport>
<div id="container"></div>
这种行为需要 defer 属性,因为默认行为需要与之前的版本兼容。
onWatcherCleanup()
3.5 引入了一个全局导入的 API onWatcherCleanup(),用于在监听器中注册清理回调函数:
import { watch, onWatcherCleanup } from 'vue'
watch(id, (newId) => {
const controller = new AbortController()
fetch(`/api/${newId}`, { signal: controller.signal }).then(() => {
// callback logic
})
onWatcherCleanup(() => {
// abort stale request
controller.abort()
})
})