Эта статья — перевод оригинальной статьи "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.
Обсуждение: RFC#436
Относится к generic defineComponent() - PR#7963
Более эргономичный 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 году мы намерены начать выпускать более мелкие и частые релизы функций. Следите за новостями!