likes
comments
collection
share

【vue3.3】 新特性 - 初体验

作者站长头像
站长
· 阅读数 55

背景

最近看到 vue3.3 版本的发布,正好最近有空,在公司很少时间经手 vue 的项目,现在正好体验一把刚发布的 API。

【vue3.3】 新特性 - 初体验

如上按官方公告文档所说,需要升级所列出依赖包的版本;下面也是列出来包含了哪些新特性,下面我们一一介绍。

TS类型支持

<script setup> 下,支持 ts 类型外部导入

之前我们在使用 definePropsdefineEmits 类型定义时只限在当前文件且只支持 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 参数时,会受到类型约束校验

【vue3.3】 新特性 - 初体验

<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 用其解构的方式仍可保留响应式

下面我们来看案例:

【vue3.3】 新特性 - 初体验

【vue3.3】 新特性 - 初体验

我们可以看到被解构的 num 值同样可以被 watchEffect 监听到,也就是说它是具有响应式的。这可能对新手来说有点懵逼,所以目前来说也是试用性的,也是在征求大家的观点和看法。

【vue3.3】 新特性 - 初体验

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 类型呢,它的出现解决了什么问题呢,下面我们构造一个场景:

【vue3.3】 新特性 - 初体验

【vue3.3】 新特性 - 初体验

当我们尝试在父组件中修改 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
评论
请登录