Vue3.5 发布正式版了,带来了哪些新变化?Vue3.5 正式发布,代号: "Tengen Toppa Gurren
Vue3.5 正式发布,代号: "Tengen Toppa Gurren Lagann"! (天元突破红莲螺岩)
有关更改和新功能的完整列表,请查阅 GitHub 上的完整更新日志
响应式系统的优化
关于响应式系统的优化,其实尤大在今年的 VueConf 2024 上也谈到了这一点。
- 降低了 56% 的内存占用
- 大数组遍历操作 10 倍性能提升
响应式 Props 解构 (Reactive Props Destructure)
其实这一特性早在 Vue3.4 中就引入了,不过当时还是 实验性 的,到了 Vue3.5 才终于转正。
这一特性的引入极大地简化了解构的同时使用默认值来声明 props
的过程。
Vue 3.5 之前:
const props = withDefaults(
defineProps<{
count?: number
msg?: string
}>(),
{
count: 0,
msg: 'hello'
}
)
Vue 3.5:
const { count = 0, msg = 'hello' } = defineProps<{
count?: number
message?: string
}>()
可以看到,代码量大大减少。而且更重要的是,解构出来的属性也是响应式的:
App.vue
:
<script setup>
import { ref } from 'vue'
import Counter from './Counter.vue'
const count = ref(0)
</script>
<template>
<button @click="count++">increment</button>
<Counter :count="count" />
</template>
Counter.vue
:
<script setup>
import { ref, defineProps, watch, watchEffect } from 'vue';
const { count } = defineProps(['count'])
// 需要传入一个 getter
watch(() => count, () => {
console.log('watch count')
})
// count 发生变化会重新执行回调
watchEffect(() => {
console.log(count)
})
</script>
<template>
{{ count }}
</template>
为什么要引入这一特性❔
在 <script setup>
中使用 defineProps
时会有以下两个痛点:
- 对 props 数据解构后,会丢失其响应性
- 结合 TS 后,声明 props 的默认值需要使用
withDefaults()
API
为了解决这个问题,Vue3.4 引入了 响应式 Props 解构 (Reactive Props Destructure) 这一实验特性,通过应用编译时的 transform 来处理 props 解构。
<script setup lang="ts">
interface Props {
msg: string
count?: number
foo?: string
}
const {
msg,
// default value just works
count = 1,
// local aliasing also just works
// here we are aliasing `props.foo` to `bar`
foo: bar
} = defineProps<Props>()
watchEffect(() => {
// will log whenever the props change
console.log(msg, count, bar)
})
</script>
上面的代码会被编译为下面的运行时代码:
export default {
props: {
msg: { type: String, required: true },
count: { type: Number, default: 1 },
foo: String
},
setup(props) {
watchEffect(() => {
console.log(props.msg, props.count, props.foo)
})
}
}
可以看到,在编译后的代码中,watchEffect
中原本对 props
属性的直接访问变为 props.xxx
的形式来访问了。
我们知道,props
是一个响应式对象,当通过 props.xxx
来访问属性时,对应的代理对象 (props)、对象属性和副作用函数就会建立关联,这样一来,当 props 属性的值,比如 count
发生变化时,watchEffect
的副作用函数就会重新执行,大概这也是 响应式 Props 解构
这一名称的由来。
useTemplateRef()
3.5 引入了一种通过 useTemplateRef() API 获取模板引用的新方法:
<script setup>
import { useTemplateRef } from 'vue'
const inputRef = useTemplateRef('input')
</script>
<template>
<input ref="input">
</template>
在 3.5 之前,我们建议使用变量名与静态 ref 属性相匹配的普通 ref。旧方法要求 ref 属性可被编译器分析,因此仅限于静态 ref 属性。相比之下,useTemplateRef()
通过运行时字符串 ID 来匹配 ref,因此支持动态 ref 绑定到变化的 ID。
Deferred Teleport
内置的 <Teleport>
组件的一个已知限制是,它的目标元素必须在 Teleport 组件挂载时已经存在。这一限制使用户无法将内容传送到由 Vue 在 Teleport 之后渲染的其他元素。
在 3.5 中,我们为 引入了一个 defer
属性,它会在当前渲染周期之后挂载,因此现在可以正常工作了:
<Teleport defer target="#container">...</Teleport>
<div id="container"></div>
写个 demo 模拟下效果:
<script setup>
import { ref } from 'vue'
const open = ref(false)
</script>
<template>
<button @click="open = true">Open Modal</button>
<Teleport defer to="#container">
<div v-if="open" class="modal">
<p>Hello from the modal!</p>
<button @click="open = false">Close</button>
</div>
</Teleport>
<div id="container"></div>
</template>
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()
})
})
当 id
状态改变,在重新执行 watch
的回调前,这里注册的清除函数会被执行,从而取消了上一次旧的请求。
这一特性有点类似 React 中的
useEffect()
中返回的清除函数(cleanup function),当依赖改变,会先执行 清除函数,然后再重新执行回调函数。
这里来写个简单的 demo,模拟下场景:快速连续的点击 +1
按钮,可以看到 network 面板上旧的请求都被取消了。
<script setup>
import { ref, watch, getCurrentWatcher, onWatcherCleanup } from 'vue'
const count = ref(0)
const getCats = async (options = {}) => {
const controller = new AbortController()
if (getCurrentWatcher()) {
onWatcherCleanup(() => {
// abort stale request
controller.abort()
})
}
return fetch(`https://api.thecatapi.com/v1/images/search?limit=10`, { ...options, signal: controller.signal })
}
watch(count, () => {
getCats()
})
</script>
<template>
<button @click="() => count++">+1</button>
{{ count }}
</template>
其他
还有一些有关 SSR 改进的特性这里就不赘述了,详情请查看 SSR Improvements
参考
转载自:https://juejin.cn/post/7410438329239420962