Vue3.3 发布:十分钟速递

Vue3.3 发布:十分钟速递

大家好,现在是 2023 年 5 月 13 日上午。不知道大家有没有注意到,前天Vue发布了3.3版本。我今天才看了一下,新的一些特性非常有用,因此想在这里给还没看过的开发者朋友稍微介绍一下最新的特性和使用案例。

s “This release is focused on developer experience improvements” - 此版本主要专注开发人员的体验提升

总结

“献给时间紧急的朋友 ”

Vue 3.3 已发布,改进了开发人员体验、新语法和宏以及对 TypeScript 的改进。该版本包括通用组件、定义插槽和emit类型、定义组件选项以及响应式props解构和定义模型语法糖等实验性功能。该版本还包括弃用 Reactivity Transform。新宏之一,defineOptions,用于定义 Options API 选项,可用于定义除 props、emits、expose 和 slot 之外的任何选项。另一个新功能是 hoistStatic,它通过将常量声明提升到顶层来优化 SFC 编译器。最后,defineModel 是一个新的语法糖宏,用于定义双向绑定 props。

单文件组件类型导入

了解React开发的朋友可能在接触Vue3.2+TypeScript时对于单文件组件无法导入外部 TypeScript 类型而感到困惑和遗憾,其原因在于:

This is because Vue needs to be able to analyze the properties on the props interface in order to generate corresponding runtime options.

「Vue 需要能够分析 props 接口上的属性,以便生成相应的运行时选项。」框架底层的限制让开发者只能在定义DefinePropsdefineEmits的时候使用本地 TypeScript type 或 interface。

3.3 版本终于解决了这个问题(开发团队在 Github issue 里曾承诺会尽快解决这个问题,他们做到了),我们可以从外部导入 TypeScript 类型了:

<script setup lang="ts">
import type { Props } from './foo'

// imported + intersection type
defineProps<Props & { extraProp?: string }>()
</script>

除此之外,还可以从第三方的包导入其 TypeScript 类型或接口,亦或是引用全局类型描述文件".d.ts"内的类型。例如:

import type { Props } from 'some-package'
defineProps<Props>()

但需要注意的是,开发者不能在 props 类型中使用条件类型:

import type { Props } from './foo'
// will throw a compiler error
defineProps<Props extends object ? Props : {}>()

究其原因,还是因为这个特性是基于AST解析的,而非TypeScript的支持。

泛型组件(Generic Component)

直接看代码:

<script setup lang="ts" generic="T">
defineProps<{
  items: T[]
  selected: T
}>()
</script>

亦或是在泛型定义的时候继承内置或从第三方导入的类型:

<script setup lang="ts" generic="T extends MyItem">
  interface MyItem {
    name: string;
    foo: number
  }

  defineProps<{
    list: T[],
    modelValue?: T
  }>
</script>

使用泛型组件时,要注意更新volarvue-tsc,在调用组件时传参将会变得更加灵活。

诚然,这个泛型组件笔者没有写过,对此也不够了解,十分抱歉。如果你对此很有兴趣,推荐看看讨论区:Generic component enhancements - Discussion #436[1]

更优雅的 DefineEmits

此前,TypeScript 和 Vue 结合定义 DefineEmits代码只支持如下调用签名的语法:

<script setup lang="ts">
const emits = defineEmits<{
  (evt: 'update:modelValue', value: string): void
  (evt: 'change'): void
}>()

笔者的体验或许跟大多数人一样,这样的写法有点冗长。在3.3版本我们可以这样写了:

// ⬇️ Vue 3.3 之后
const emits = defineEmits<{
  'update:modelValue': [value: string],
  'change': []
}>()
</script>

在类型定义中,键就是事件名,值就是函数参数。「新写法不是必须的,你依然可以使用旧的写法」

笔者较懒,推荐后者

DefineSlots

这是一个全新的特性

这个特性可以让我们显式地定义插槽的类型,这个特性对于复杂的组件较为有用,来看看下面这个配合泛型组件的示例(代码源于参考文章):

分页组件:

<script setup lang="ts" generic="T">
// 子组件 Paginator
defineProps<{
  data: T[]
}>()

defineSlots<{
  default(props: { item: T }): any
}>()
</script>

使用:

<template>
  <!-- 父组件 -->
  <Paginator :data="[1, 2, 3]">
    <template #default="{ item }">{{ item }}</template>
  </Paginator>
</template>

显式定义类型优于隐式定义类型,有助于提高代码的可读性。

实验特性

试验性的特性都需要在编译工具的配置中开启试验性属性。

Props 响应式解构

此特性可以让我们少写一些代码:

自动保持响应式解构,并且可以使用非常符合 JavaScript 的语法的方式设置默认值。

<script setup>
import { watchEffect } from 'vue'

const { msg = 'hello' } = defineProps(['msg'])

watchEffect(() => {
  // accessing `msg` in watchers and computed getters
  // tracks it as a dependency, just like accessing `props.msg`
  console.log(`msg is: ${msg}`)
})
</script>

<template>{{ msg }}</template>

如果你想用 TypeScript,则:

const { count } = defineProps<{ count: 0 }>()

此外,记住开启试验性配置:

笔者使用vite编译项目:

// vite.config.js
export default {
  plugins: [
    vue({
      script: {
        propsDestructure: true
      }
    })
  ]
}

defineModel

此前,编写支持v-model组件时需要声明propsemit,代码如下所示:

<!-- 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实验性属性就可以这样写:

<!-- AFTER -->
<script setup>
const modelValue = defineModel()
console.log(modelValue.value)
</script>

<template>
  <input v-model="modelValue" />
</template>

显著降低代码量,懒人必备,早点下班回家撸猫才是正经。

DefineOptions 宏定义

笔者在写缓存组件的时候,发现要定义组件的名字,必须在<script setup>之外再使用<script>来声明组件名(我也不需要使用默认的文件名)。实在是麻烦,于是搜索到了 **Kevin Deng **的unplugin-vue-define-options - npm[2] 插件来实现在setup里面定义组件名。

现在,感谢作者的贡献,我们终于可以在3.3版本直接使用内置功能了:

<script setup>
defineOptions({
  name: 'Foo',
  inheritAttrs: false,
  // ... 更多自定义属性
})
</script>

基础设施完善

除了以上更新之外,开发团队还更新了一些关于基础设施的内容:

  • 提升静态常量,这个属于编译器优化
  • 提高构建速度,使用rollup-plugin-esbuild
  • 提高测试速度:从JestVitest(OK,大家转到Vitest来吧)
  • 加速类型生成,使用rollup-plugin-dts
  • 增加回归测试

参考文档

  • Announcing Vue 3.3 | The Vue Point[3]
  • Vue 3.3 主要新特性详解 - 三咲智子 Kevin Deng[4]

参考资料

[1] Generic component enhancements - Discussion #436: github.com/vuejs/rfcs/d

[2] unplugin-vue-define-options - npm: npmjs.com/package/unplu

[3] Announcing Vue 3.3 | The Vue Point: blog.vuejs.org/posts/vu

[4] Vue 3.3 主要新特性详解 - 三咲智子 Kevin Deng: xlog.sxzz.moe/vue-3-3

发布于 2023-05-13 13:53・IP 属地福建