Команда Vue сообщила о выпуске версии 3.3 — «Rurouni Kenshin».
В новой версии разработчики постарались улучшить опыт разработки. К примеру, было улучшено взаимодействие с SFC <script setup> на TypeScript.
Также были решены многие давние проблемы с использованием Vue и TypeScript.
Обновления зависимостей
Чтобы обновиться до Vue 3.3, нужно также обновить зависимости:
Поддержка импортированных и комплексных типов в макросах
До версии 3.3 типы в defineProps и defineEmits могли быть только локальными типами и поддерживали только литералы типов.
Java разработчик (проект по созданию системы класса IDM) АО «Гринатом», , можно удалённо, По итогам собеседования tproger.ru Вакансии на tproger.ru
Так было потому, что 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.
Текущие ограничения
Существует также опция слотов для использования 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-модели, ему необходимо было объявить свойство и создать событие updateropName, чтобы свойство обновлялось:
<!— 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:
***
В этом посте рассмотрели основные изменения в версии 3.3. Ознакомиться с полным списком обновлений можно на GitHub.
В новой версии разработчики постарались улучшить опыт разработки. К примеру, было улучшено взаимодействие с SFC <script setup> на TypeScript.
Также были решены многие давние проблемы с использованием Vue и TypeScript.
Основные изменения
Обновления зависимостей
Чтобы обновиться до Vue 3.3, нужно также обновить зависимости:
- volar / vue-tsc@^1.6.4;
- vite@^4.3.5;
- @vitejs/plugin-vue@^4.2.0;
- vue-loader@^17.1.0 (если используется webpack или vue-cli).
Поддержка импортированных и комплексных типов в макросах
До версии 3.3 типы в defineProps и defineEmits могли быть только локальными типами и поддерживали только литералы типов.
Java разработчик (проект по созданию системы класса IDM) АО «Гринатом», , можно удалённо, По итогам собеседования tproger.ru Вакансии на tproger.ru
Так было потому, что 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.
Текущие ограничения
- Проверка слотов еще не реализована в volar/vue-tsc.
- Тип возврата функции слота может быть любым, но в будущем он может использоваться для проверки содержимого слота.
Существует также опция слотов для использования 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-модели, ему необходимо было объявить свойство и создать событие updateropName, чтобы свойство обновлялось:
<!— 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:
- Сборки стали в 10 раз быстрее благодаря отделению проверки типов от сводной сборки и переходу от rollup-plugin-typescript2 к rollup-plugin-esbuild.
- Ускорились тесты за счет перехода с Jest на Vitest.
- Ускорилось создание типов за счет перехода от @Microsoft/api-extractor к rollup-plugin-dts.
- Комплексные регрессионные тесты с помощью ecosystem-ci позволяют выявлять регрессии в основных зависимостях перед выпуском.
***
В этом посте рассмотрели основные изменения в версии 3.3. Ознакомиться с полным списком обновлений можно на GitHub.