【vue3.3】 新特性 - 初体验
背景
最近看到 vue3.3 版本的发布,正好最近有空,在公司很少时间经手 vue 的项目,现在正好体验一把刚发布的 API。
如上按官方公告文档所说,需要升级所列出依赖包的版本;下面也是列出来包含了哪些新特性,下面我们一一介绍。
TS类型支持
在 <script setup>
下,支持 ts 类型外部导入
之前我们在使用 defineProps
和 defineEmits
类型定义时只限在当前文件且只支持 type |interface 的类型
现在 3.3 的版本 已经支持 外部文件导入 的方式且支持复杂类型的书写
下面我们先定义一组类型:
// userType.ts
interface UserProps {
name: string
}
然后在组件中去使用:
// user.vue
<template>
<div></div>
</template>
<script setup lang="ts">
import type { UserProps } from './userType'
// imported + intersection type
defineProps<UserProps & { extraProp?: string }>()
</script>
下面我们在组件中使用上面### defineEmits
### defineEmits
### defineEmits
### defineEmits
组件:
// App.vue
<template>
<User name="ls"></User>
</template>
<script setup lang="ts">
import User from "./user.vue";
</script>
当我们传递 name
参数时,会受到类型约束校验
在 <script setup>
下,支持范型类型参数
使用 generic
字段传入范型类型;还能使用多个参数、扩展约束、默认类型和引用导入类型:
下面我们使用 T
来自动推断出我们传入的类型是什么,它会做相应类型的映射:
<script setup lang="ts" generic="T">
defineProps<{
items: T[]
selected: T
}>()
</script>
我们还可以使用多个参数,extends
约束,默认类型和引用导入的类型:
<script
setup
lang="ts"
generic="T extends string | number, U extends Item"
>
import type { Item } from './types'
defineProps<{
id: T
list: U[]
}>()
</script>
defineEmits
在类型声明 上的优化
之前的写法使用的是 调用签名 的方式,事件名 是放在 第一个入参的,如:e: 'foo'
,后面的若干个参数是第一个函数的入参。
// BEFORE
const emit = defineEmits<{
(e: 'foo', id: number): void
(e: 'bar', name: string, ...rest: any[]): void
}>()
现在的写法是 使用 key 作为事件名,value 写成了数组的形式来表示入参的类型和参数的个数,这更符合编程人的写法。但之前的写法也是支持的。
// AFTER
const emit = defineEmits<{
foo: [id: number]
bar: [name: string, ...rest: any[]]
}>()
defineSlots
对类型的支持
default
为 匿名插槽
的类型声明,foo
则是 作用域插槽
对参数的约束。
<template>
<div>
<!-- 匿名插槽 -->
<slot msg="msg"></slot>
<!-- 作用域插槽 -->
<slot name="foo" :id="1"></slot>
</div>
</template>
<script setup lang="ts">
defineSlots<{
default: (props: { msg: string }) => any;
foo: (props: { id: number }) => any;
}>();
</script>
<style scoped></style>
实验性的 API
使用下面 API 的前提是 需要在 vite.config.ts 文件中配置
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue({
script: {
propsDestructure: true, // 解构props
defineModel: true // defineModel
},
}),
],
});
defineProps
用其解构的方式仍可保留响应式
下面我们来看案例:
我们可以看到被解构的 num
值同样可以被 watchEffect
监听到,也就是说它是具有响应式的。这可能对新手来说有点懵逼,所以目前来说也是试用性的,也是在征求大家的观点和看法。
defineModel
简化之前的代码写法
在之前,我们想在组件上实现 v-model
的功能, 需要写以下的代码:
<!-- BEFORE -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
console.log(props.modelValue)
function onInput(e) {
emit('update:modelValue', e.target.value)
}
</script>
<template>
<input :value="modelValue" @input="onInput" />
</template>
现在, defineModel
它帮我们做了上述的逻辑,自动的注册 props
值,并且返回一个具有响应式的 ref
值。
<!-- AFTER -->
<script setup>
const modelValue = defineModel()
console.log(modelValue.value)
</script>
<template>
<input v-model="modelValue" />
</template>
其他显著的特性
defineOptions
在缓存组件时,需要设置 name
属性, 但在 <script setup>
,并不能达到我们的目的,所以我们还需要再写一个 <script>
来支持,比较麻烦,现在可以直接使用该内置功能了:
<script setup>
defineOptions({
name: 'Foo',
inheritAttrs: false, // 禁止传入的属性添加到组件的根元素上
// ... 更多自定义属性
})
</script>
toRef
在使用上作了升级
toRef
是之前存在的API,现在新增了一个重载: getter
类型;以这种方式创建的 ref
是只读的,只是在每次访问 .value
时调用 getter
。
const ref = toRef(() => 123)
ref.value // 123
为什么新增了 getter
类型呢,它的出现解决了什么问题呢,下面我们构造一个场景:
当我们尝试在父组件中修改 userInfo
的引用关系时,watchEffect
将不会再触发,这也是 toRef
之前的传参方式;下面我们将子组件代码修改:
<template></template>
<script setup lang="ts">
import { toRef, watchEffect } from "vue";
const props = defineProps<{ userInfo: { name: string } }>();
- useName(toRef(props.userInfo, "name"));
+ useName(toRef(() => props.userInfo.name));
function useName(name: any) {
watchEffect(() => {
console.log(`watchEffect: ${name.value}`);
});
}
</script>
<style scoped></style>
上面我们将传参方式改成了 getter
的方式,当修改外层引用关系的时候,watchEffect
也同样会触发。那按照之前的解决办法可以写成 computed
的形式,这样也是可以的,但 computed
创建一个单独的 effect
来缓存计算值。当 getter
只访问属性而不执行任何昂贵的计算时,这实际上是很大的开销。
新增了 toValue
API
toValue
是相反于 toRef
的,有三种转换形式: ref --> value、value --> value、getter --> value
;
toValue(() => 2) // 2
unRef(() => 2) // () => 2
可以看到,相比于 unRef
来说,它得到的是函数返回值,所以针对不同的场景来灵活使用不同的API。
剩下的就是框架内部做的优化了,如对jsx类型增加命名空间,内部依赖包的变动。
转载自:https://juejin.cn/post/7241080533169520698