【翻】Announcing Vue 3.3

为了提高英语水平和保持技术成长,开始按计划翻译一些短篇和博客,有问题欢迎讨论👻
原文:Announcing Vue 3.3
原作者:Evan You

正文

今天,我们很高兴地宣布Vue3.3”Rurouni Kenshin”正式发布!

这次版本的重点改动在于提升开发人员的开发体验,特别是SFC的<script setup>和TypeScript的使用。并且与Vue配套的的开发工具(Volar 1.6)一起,我们解决了在使用Vue和Typecript时长期存在的许多痛点。

本文章会大致介绍3.3中的重点功能。有关3.3版本变更的完整内容。请查看Gitub上的changelog

配套库更新
当升级到3.3时,建议同时更新以下相关库:
volar / vue-tsc@^1.6.4
vite@^4.3.5
@vitejs/plugin-vue@^4.2.0
vue-loader@^17.1.0 (if using webpack or vue-cli)

<script setup> + TypeScript DX Improvements

Imported and Complex Types Support in Macros

以前,在definePropsdefineEmits的类型参数中使用的类型仅限于本地类型,并且只支持type literalsinterfaces。只是因为Vue需要分析props interface上的属性,以便能够生产相应的运行时选项。

这个限制在3.3中得以解决。编译器现在可以解析导入类型,并支持一些复杂类型集合:

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

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

注意,复杂类型的支持是基于AST的,所以并非100%支持。某些需要实际类型分析的复杂类型,比如条件类型,并不支持。你可以对单个prop使用条件类型,但不能对整个prop对象使用条件类型。

Generic Components

使用<script setup>的组件现在可以通过generic属性接收通用类型参数:

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

generic的值与TypeScript中参数列表的工作方式完全一样。比如可以使用多个参数,通过extends约束,默认类型和引用导入类型:

<script setup lang="ts" generic="T extends string | number, U extends Item">
import type { Item } from './types'
defineProps<{
  id: T
  list: U[]
}>()
</script>

该功能以前需要明确选择加入,但现在在最新版本的volar/vue-tsc中已经默认启用。

More Ergonomic defineEmits

以前,defineEmits的类型参数只支持调用签名语法:

// BEFORE
const emit = defineEmits<{
  (e: 'foo', id: number): void
  (e: 'bar', name: string, ...rest: any[]): void
}>()

该类型与emit的返回类型相匹配,但写起来有点啰嗦且不好看。然后3.3引入了一种更符合工学的声明方式:

// AFTER
const emit = defineEmits<{
  foo: [id: number]
  bar: [name: string, ...rest: any[]]
}>()

在上面的类型中,key是事件名称,value是额外参数的数组类型。虽然不是必须的,但你也可以使用元祖类型来明确参数,如上面的示例。

并且现在依然支持调用签名语法。

Typed Slots with defineSlots

新的defineSlots宏可用于声明slots和slots的props

<script setup lang="ts">
defineSlots<{
  default?: (props: { msg: string }) => any
  item?: (props: { id: number }) => any
}>()
</script>

defineSlots()只接受一个类型参数,并且不接受运行时参数。然后类型参数应该是一个type literals,其中的key是插槽名字,value是一个插槽函数。函数的第一个参数是插槽期望接受的props,其类型将用于模版中slot的props。defineSlots的返回值与useSlots返回的插槽对象相同。

目前的一些限制:

  • volar/vue-tsc目前尚未实现所需的插槽检查。
  • 插槽函数的返回类型目前还是忽视的,可以是任何类型,但我们将来可能会利用它进行插槽内容检查。

还有一个相应的slots选项适用于defineComponent的使用。这两个API都没有实现运行时,纯粹是IDE和vue-tsc的类型提示。

Experimental Features

Reactive Props Destructure

之前的响应式转换中未实现props的解构响应,现在已经被拆分为一个独立的功能。

该功能允许解构的props保留响应式,并提供了一种更符合工学的方式来声明props默认值:

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

这个目前是实验性的功能,需要明确选择加入。

defineModel

之前,如果一个组件要支持v-moel的双向绑定,首先需要声明一个prop,然后在更新时发出相应的update:propName事件:

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

在3.3中通过新的APIdefineModel简化来其用法。该宏会自动注册一个prop,并返回一个可以直接更改的ref

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

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

这个目前是实验性的功能,需要明确选择加入。

Other Notable Features

defineOptions

新的defineOptions宏直接可以在<script setup>中声明组件选项,而不需要单独的<script>块:

<script setup>
defineOptions({ inheritAttrs: false })
</script>

Better Getter Support with toRef and toValue

toRef得到增强,支持 values/getters/existing refs统一转为refs

// equivalent to ref(1)
toRef(1)
// creates a readonly ref that calls the getter on .value access
toRef(() => props.foo)
// returns existing refs as-is
toRef(existingRef)

通过 getter 调用toRefcomputed类似,但是在只进行属性访问而不是高度计算时,toRef效率会更高

toValue是提供了一个相反的方法,把 values/getters/ref 统一转为普通值

toValue(1) //       --> 1
toValue(ref(1)) //  --> 1
toValue(() => 1) // --> 1

toValue可以在组合式函数中代替unref,这样你的组合式函数就可以接受getters作为响应式数据源:

// before: allocating unnecessary intermediate refs
useFeature(computed(() => props.foo))
useFeature(toRef(props, 'foo'))

// after: more efficient and succinct
useFeature(() => props.foo)

toReftoValue之间的关系就类似于refunref之间的关系,主要区别在于对getter函数的特殊处理

JSX Import Source Support

目前,Vue的类型会自动注册全局JSX类型,这可能会与需要JSX类型推断的库产生冲突,尤其是React

从3.3开始,Vue支持通过TypeScript的jsxImportSource选项指定JSX命名空间。这允许用户根据自己的使用情况选择全局或者按文件选额。

为了向后兼容,3.3版本仍会全局注册JSX命名空间。我们计划在3.4中移除默认的全局注册。如果你在Vue中使用TSX,在升级到3.3后,应在tsconfig.json中显式添加jsxImportSource,避免在3.4中出现中断。

Maintenance Infrastructure Improvements

此次版本升级基于许多维护基础构造的改进,使我们有更大信心加快工作进度:

  • 通过将类型检查与rollup构建分离,并将rollup-plugin-typescript2迁为rollup-plugin-esbuild,将构建速度提高了10倍。
  • 把 Jest 迁为 Vitest,加快了测试速度。
  • @microsoft/api-extractor迁为rollup-plugin-dts,更快地生成类型
  • 通过ecosystem-ci进行全面的回归测试(在发布前捕捉主要生态系统依赖的回归)

按照计划,我们的目标是在2023年开始更小规模、更频繁地发布功能。敬请期待!