Обзор Vue 3.3. Что изменилось и при чём тут TypeScript

Команда Vue сообщила о выпуске версии 3.3 "Rurouni Kenshin". Многие обновления направлены на поддержку TypeScript.

4К открытий5К показов

Команда Vue сообщила о выпуске версии 3.3 — “Rurouni Kenshin”.

В новой версии разработчики постарались улучшить опыт разработки. К примеру, было улучшено взаимодействие с SFC <script setup> на TypeScript.

Также были решены многие давние проблемы с использованием Vue и TypeScript.

Основные изменения

Обновления зависимостей

Чтобы обновиться до Vue 3.3, нужно также обновить зависимости:

  1. volar / vue-tsc@^1.6.4;
  2. vite@^4.3.5;
  3. @vitejs/plugin-vue@^4.2.0;
  4. vue-loader@^17.1.0 (если используется webpack или vue-cli).

Поддержка импортированных и комплексных типов в макросах

До версии 3.3 типы в defineProps и defineEmits могли быть только локальными типами и поддерживали только литералы типов.

Так было потому, что Vue нужно было анализировать свойства props для генерации опций во время выполнения кода.

Теперь, в версии 3.3, компилятор разрешает использовать импортированные и комплексные, сложные типы:

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

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

Обратите внимание, что поддержка сложных типов основана на AST, поэтому не все типы могут поддерживаться на 100%. К примеру, условные типы вообще не поддерживаются.

Вы можете использовать условные типы для одного параметра, но не для объекта параметров.

Универсальные компоненты

Компоненты <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, она включена по умолчанию.

Эргономичный defineEmits

Ранее параметр для defineEmits поддерживал только синтаксис сигнатуры вызова:

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

Тип соответствует возвращаемому типу для emit, но он короткий и неудобный для написания. В версии 3.3 ввели более эргономичный способ объявления emit:

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

В литерале типа ключ — это имя события, а значение — тип массива, определяющий дополнительные аргументы.

Старый синтаксис сигнатуры всё ещё поддерживается.

Типизированные слоты с defineSlots

Новый макрос defineSlots можно использовать для объявления ожидаемых слотов и их свойств:

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

defineSlots() принимает только параметр типа, но не принимает аргументы рантайма.

Параметр типа должен быть литералом типа, где ключ свойства — это имя слота, а значение — функция слота.

Первый аргумент функции — props, тип которых будет использоваться для пропсов слота в шаблоне.

Значение defineSlots — это тот же объект слотов, который возвращается из useSlots.

Текущие ограничения

  1. Проверка слотов еще не реализована в volar/vue-tsc.
  2. Тип возврата функции слота может быть любым, но в будущем он может использоваться для проверки содержимого слота.

Существует также опция слотов для использования defineComponent. Оба API используются исключительно в качестве подсказок типа для IDE и vue-tsc.

Экспериментальные функции

Деструктуризация реактивных пропсов

Деструктура реактивных пропсов ранее была частью преобразования реактивности, которое теперь удалено. Сейчас она выделена в отдельную функцию.

Эта функция позволяет деструктурированным пропсам сохранять реактивность и предлагает более удобный способ объявления значений:

			<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-модели, ему необходимо было объявить свойство и создать событие 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 упрощает этот процесс с помощью макроса defineModel. Макрос автоматически объявляет свойство и возвращает ссылку:

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

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

Эта функция является экспериментальной и требует явного согласия.

Другие примечательные особенности

defineOptions

Новый макрос defineOptions позволяет объявлять параметры компонента прямо в <script setup>. При этом не нужен отдельный блок <script>:

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

Лучшая поддержка Getter с toRef и toValue

toRef улучшен для поддержки нормализации значений, геттеров и 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)
		

Вызов toRef может быть более эффективным, если геттеру нужно просто выполнить доступ к свойствам. Для этого не понадобится долгих и сложных вычислений.

Новый служебный метод toValue делает всё наоборот, нормализуя всё в значения:

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

toValue можно использовать в составных элементах вместо unref, чтобы компоненты принимали геттеры в качестве реактивных источников данных:

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

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

Разница между toRef и toValue такая же, как между ref и unref. Разница только в обработке функций-получателей.

Импорт исходного кода JSX

Сейчас типы Vue автоматически регистрируют глобальную типизацию JSX. Это может привести к конфликту при использовании других библиотек, которым нужно определять типы JSX. В частности, речь идёт о React.

Начиная с версии 3.3, Vue поддерживает указание типизаций JSX с помощью параметра TypeScript jsxImportSource

Улучшение инфраструктуры обслуживания

Вот, что улучшили в выпуске 3.3:

  1. Сборки стали в 10 раз быстрее благодаря отделению проверки типов от сводной сборки и переходу от rollup-plugin-typescript2 к rollup-plugin-esbuild.
  2. Ускорились тесты за счет перехода с Jest на Vitest.
  3. Ускорилось создание типов за счет перехода от @microsoft/api-extractor к rollup-plugin-dts.
  4. Комплексные регрессионные тесты с помощью ecosystem-ci позволяют выявлять регрессии в основных зависимостях перед выпуском.

В этом посте рассмотрели основные изменения в версии 3.3. Ознакомиться с полным списком обновлений можно на GitHub.

Следите за новыми постами
Следите за новыми постами по любимым темам
4К открытий5К показов