Эта статья — перевод оригинальной статьи "Announcing Vue 3.3".

Также я веду телеграм канал “Frontend по-флотски”, где рассказываю про интересные вещи из мира разработки интерфейсов.

Вступление

Сегодня мы рады объявить о релизе Vue 3.3 "Rurouni Kenshin"!

Этот выпуск сосредоточен на улучшении опыта разработки - в частности, на использовании SFC <script setup> с TypeScript. Вместе с релизом 1.6 Vue Language Tools (ранее известного как Volar) мы решили многие давние проблемы при использовании Vue с TypeScript.

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

<script setup> + Улучшения TypeScript DX

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

Ранее типы, используемые в параметре типа в defineProps и defineEmits, были ограничены локальными типами и поддерживали только литералы типов и интерфейсы. Это связано с тем, что Vue должен иметь возможность анализировать свойства интерфейса props для генерации соответствующих опций во время выполнения.

Теперь это ограничение устранено в версии 3.3. Компилятор теперь может разрешать импортированные типы и поддерживает ограниченный набор сложных типов:

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

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

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

Подробнее: PR#8083

Обобщенные компоненты

Компоненты, использующие <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 поддерживал только синтаксис сигнатуры вызова:

// Раньше
const emit = defineEmits<{
  (e: 'foo', id: number): void
  (e: 'bar', name: string, ...rest: any[]): void
}>()

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

// Теперь
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() принимает только параметр типа и никаких рантайм аргументов. Параметр типа должен быть литералом типа, где ключ свойства - имя слота, а значение - функция слота. Первым аргументом функции является проп, который слот ожидает получить, и его тип будет использоваться для пропсов слота в шаблоне. Возвращаемое значение defineSlots - это тот же объект слота, который возвращается из useSlots.

Некоторые текущие ограничения:

  • Проверка требуемых слотов пока не реализована в volar / vue-tsc.

  • Тип возврата функции слота в настоящее время игнорируется и может быть any, но мы можем использовать его для проверки содержимого слота в будущем.

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

Подробнее: PR#7982

Экспериментальные возможности

Реактивная деструктуризация пропсов

Ранее являвшаяся частью исчезнувшего Reactivity Transform, реактивная деструктуризация пропсов была выделена в отдельную функцию.

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

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

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

Подробнее: RFC#502

defineModel

Ранее, чтобы компонент поддерживал двустороннее связывание с v-model, он должен был (1) объявить пропс и (2) вызвать соответствующее событие update:propName, когда он намеревался обновить пропс:

<!-- ДО -->
<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. Макрос автоматически регистрирует проп и возвращает ref, который может быть непосредственно изменен:

<!-- ПОСЛЕ -->
<script setup>
const modelValue = defineModel()
console.log(modelValue.value)
</script>

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

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

Подробнее: RFC#503

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

defineOptions

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

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

Улучшенная поддержка геттеров с помощью toRef и toValue

toRef был улучшен для поддержки нормализации значений / геттеров / существующих refs внутри refs:

// Равносильно ref(1)
toRef(1)
// создает readonly ref, который вызывает getter при доступе к .value
toRef(() => props.foo)
// Возвращает существующий refs как есть
toRef(existingRef)

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

Новый метод toValue обеспечивает обратное, нормализуя значения/геттеры/рефы в значения:

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

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

// до: выделение ненужных промежуточных ссылок
useFeature(computed(() => props.foo))
useFeature(toRef(props, 'foo'))

// после: более эффективно и лаконично
useFeature(() => props.foo)

Отношения между toRef и toValue аналогичны отношениям между ref и unref, основное отличие заключается в особой обработке функций getter.

Подробнее: PR#7997

Поддержка импорта исходного кода JSX

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

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

Для обратной совместимости в версии 3.3 пространство имен JSX по-прежнему регистрируется глобально. Мы планируем убрать глобальную регистрацию по умолчанию в версии 3.4. Если вы используете TSX с Vue, вам следует добавить явный jsxImportSource в tsconfig.json после обновления до 3.3, чтобы избежать поломки в 3.4.

Улучшение инфраструктуры поддержки

Этот релиз основан на многочисленных улучшениях инфраструктуры поддержки, которые позволяют нам двигаться быстрее и увереннее:

  • Сборка в 10 раз быстрее благодаря отделению проверки типов от сборки rollup и переходу от rollup-plugin-typescript2 к rollup-plugin-esbuild.

  • Более быстрые тесты благодаря переходу от Jest к Vitest.

  • Более быстрая генерация типов за счет перехода от @microsoft/api-extractor к rollup-plugin-dts.

  • Комплексные регрессионные тесты с помощью ecosystem-ci - отлавливают регрессии в основных зависимых компонентах экосистемы до выпуска релизов!

Как и планировалось, в 2023 году мы намерены начать выпускать более мелкие и частые релизы функций. Следите за новостями!

Комментарии (0)