📢 vue3.5正式发布,解构Props

1,387 阅读3分钟

原文地址:alvis.org.cn/posts/c0804…

前言 📝

在 3.5 中,Vue 的反应性系统经历了另一次重大重构,实现了更好的性能并显着提高了内存使用率 ( -56%  ),而行为没有变化。重构还解决了 SSR 期间因计算挂起而导致的陈旧计算值和内存问题。

此外,3.5 还优化了大型、深度反应阵列的反应跟踪,在某些情况下使此类操作速度提高了 10 倍。

以上内容翻译自Vue官方博客

1. 响应式Props解构

以前我们对Props直接进行解构赋值是会失去响应式的,需要配合使用toRefs或者toRef解构才会有响应式,那么就多了toRefs或者toRef这工序,而最新Vue3.5版本已经不需要了。我们在3.5版本中可以直接结构,并会携带响应式。

之前的写法:

<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>
<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之后的写法:

<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 中:

// 这样是会报错的
watch(count /* ... */)

// 正确写法
watch(() => count /* ... */)

顺便说一嘴: 如果我们尝试在子组件中,修改props解构后的值。Vue同样是不允许这样操作的,关于禁止对 props 做出更改的限制依然有效。

<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>

我们简单来看一下他是怎么实现的:

<script setup lang="ts">
const { count = 0, message = "这是一条默认信息" } = defineProps<{
	count?: number
	message?: string
}>()

console.log(count)
</script>
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。

<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(),用于在观察者中注册清理回调(执行顺序是在下一次监听器执行之前执行):

<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的时候,计时器都会累加。会导致很多很多的机器时同时启动,执行多次。

<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函数,通过在下一次进入监听器之前,清除掉上一次的副作用函数。