前言 📝
在 3.5 中,Vue 的反应性系统经历了另一次重大重构,实现了更好的性能并显着提高了内存使用率 ( -56% ),而行为没有变化。重构还解决了 SSR 期间因计算挂起而导致的陈旧计算值和内存问题。
此外,3.5 还优化了大型、深度反应阵列的反应跟踪,在某些情况下使此类操作速度提高了 10 倍。
以上内容翻译自Vue官方博客
1. 响应式Props解构
以前我们对Props
直接进行解构赋值是会失去响应式的,需要配合使用toRefs
或者toRef
解构才会有响应式,那么就多了toRefs
或者toRef
这工序,而最新Vue3.5
版本已经不需要了。我们在3.5版本中可以直接结构,并会携带响应式。
之前的写法:
1 2 3 4 5 6 7 8 9 10 11 12
| <script setup lang="ts"> import { ref } from 'vue' import Comp from './Comp.vue';
const count = ref(0) const message = ref('') </script>
<template> <Comp :count="count" :message="message" /> <button @click="count++">更改count</button> </template>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <script setup lang="ts"> import { toRefs, toRef, withDefaults } from 'vue'; const props = withDefaults(defineProps<{ count?: number message?: string }>(), { count: 0, message: "这是一条默认信息" })
const { count } = toRefs(props) // 或者 // const count = toRef(props, 'count') </script>
<template> <div> 子组件展示的count: {{count}} </div> </template>
|
我们可以看到,之前的写法需要借助一些“外力”才能做到,并且在设置默认值的时候需要借助withDefaults
宏来完成。
3.5之后的写法:
1 2 3 4 5 6 7 8 9 10 11 12
| <script setup lang="ts"> const { count = 0, message = "这是一条默认信息" } = defineProps<{ count?: number message?: string }>() </script>
<template> <div> 子组件展示的count: {{count}} </div> </template>
|
对解构变量(例如count
)的访问会由编译器自动编译到props.count
中,因此在访问时会跟踪它们。与props.count
类似,观察解构的 prop 变量或将其传递到可组合项同时保留反应性需要将其包装在 getter 中:
1 2 3 4 5
| watch(count )
watch(() => count )
|
顺便说一嘴: 如果我们尝试在子组件中,修改props解构后的值。Vue同样是不允许这样操作的,关于禁止对 props 做出更改的限制依然有效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <script setup lang="ts"> const { count = 0, message = "这是一条默认信息" } = defineProps<{ count?: number message?: string }>() </script>
<template> <div> 子组件展示的count: {{count}} <!-- 这是被禁止的,不允许破坏单项数据流 --> <button @click="count++">更改props传递过来的值</button> </div> </template>
|
我们简单来看一下他是怎么实现的:
1 2 3 4 5 6 7 8
| <script setup lang="ts"> const { count = 0, message = "这是一条默认信息" } = defineProps<{ count?: number message?: string }>()
console.log(count) </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const __sfc__ = _defineComponent({ __name: 'Comp', props: { count: { type: Number, required: false, default: 0 }, message: { type: String, required: false, default: "这是一条默认信息" } }, setup(__props, { expose: __expose }) { __expose();
console.log(__props.count) const __returned__ = { } Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true }) return __returned__ }
|
从上面的代码可以看到console.log(count)
经过编译后变成了console.log(__props.count)
,这样处理后count
当然就不会丢失响应式了。实际上内部转换的时候使用的还是props.count
。
2. useTemplateRef
函数
在 3.5 之前,vue建议使用变量名与静态ref
属性匹配的普通引用。旧方法要求编译器可以分析ref
属性,因此仅限于静态ref
属性。相比之下, useTemplateRef()
通过运行时字符串 ID 匹配引用,因此支持动态引用绑定到不断变化的 ID。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <script setup> import { useTemplateRef, onMounted } from 'vue' const inputRef = useTemplateRef('input')
onMounted(() => { // <input /> console.log(inputRef.value) })
</script>
<template> <input ref="input"> </template>
|
3. onWatcherCleanup()
清空上一次watch函数内的副作用
3.5 引入了全局导入的 API onWatcherCleanup()
,用于在观察者中注册清理回调(执行顺序是在下一次监听器执行之前执行):
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <script setup lang="ts"> import { ref } from 'vue' import { onWatcherCleanup, watch } from 'vue' let id = ref(1)
watch(id, () => { const timer = setInterval(() => { id.value += 1 console.log(id.value) }, 1000) }, { immediate: true }) </script>
|
上面的代码中,我们可以想象到,每次进入watch的时候,计时器都会累加。会导致很多很多的机器时同时启动,执行多次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <script setup lang="ts"> import { ref } from 'vue' import { onWatcherCleanup, watch } from 'vue' let id = ref(1)
watch(id, () => { const timer = setInterval(() => { id.value += 1 console.log(id.value) }, 1000) // 在再次执行watch的时候先清除掉富足用函数 onWatcherCleanup(() => { clearInterval(timer) }) }, { immediate: true }) </script>
|
我们通过onWatcherCleanup
函数,通过在下一次进入监听器之前,清除掉上一次的副作用函数。