前言 📝

在 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__ = /*#__PURE__*/_defineComponent({
__name: 'Comp',
props: {
count: { type: Number, required: false, default: 0 },
message: { type: String, required: false, default: "这是一条默认信息" }
},
setup(__props, { expose: __expose }) {
__expose();

// 她编译器会自动帮助我们去加上props
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函数,通过在下一次进入监听器之前,清除掉上一次的副作用函数。