[Easy] Новые возможности Vue3
1. Какие новые возможности появились в Vue 3?
Какие основные новые возможности появились в Vue 3?
Vue 3 представил множество важных обновлений:
Основные возможности
- Composition API
- Teleport
- Fragment (множественные корневые узлы)
- Suspense
- Множественные привязки
v-model - Улучшенная поддержка TypeScript
- Улучшения производительности (меньший размер бандла, более быстрый рендеринг)
2. Teleport
Что такое Teleport?
Teleport позволяет рендерить часть компонента в другом месте дерева DOM без изменения принадлежности компонента или структуры логики.
Типичные случаи использования
Modal, Tooltip, Notification, Popover, оверлейные слои.
<template>
<div>
<button @click="showModal = true">Открыть модальное окно</button>
<Teleport to="body">
<div v-if="showModal" class="modal">
<div class="modal-content">
<h2>Заголовок модального окна</h2>
<p>Содержимое модального окна</p>
<button @click="showModal = false">Закрыть</button>
</div>
</div>
</Teleport>
</div>
</template>
<script setup>
import { ref } from 'vue';
const showModal = ref(false);
</script>
Почему Teleport полезен
- Решает проблемы stacking context / z-index
- Избегает обрезки из-за overflow предка
- Сохраняет логику компонента рядом, рендеря в другом месте
3. Fragment (множественные корневые узлы)
Что такое Fragment в Vue 3?
Vue 3 позволяет шаблону компонента иметь множественные корневые узлы.
В отличие от React, Vue использует неявные fragments (дополнительный тег <Fragment> не нужен).
Vue 2 vs Vue 3
Vue 2: требуется единственный корень.
<template>
<div>
<h1>Заголовок</h1>
<p>Содержимое</p>
</div>
</template>
Vue 3: множественные корни допустимы.
<template>
<h1>Заголовок</h1>
<p>Содержимое</p>
</template>
Почему Fragment важен
- Меньше лишних элементов-обёрток
- Лучшая семантика HTML
- Менее глубокое дерево DOM
- Чище стили и селекторы
Наследование атрибутов в компонентах с множественными корнями
При использовании шаблонов с множественными корнями атрибуты родителя (class, id и т.д.) не применяются автоматически к конкретному корню.
Используйте $attrs вручную.
<!-- Родитель -->
<MyComponent class="custom-class" id="my-id" />
<!-- Дочерний -->
<template>
<div v-bind="$attrs">Первый корень</div>
<div>Второй корень</div>
</template>
Вы можете управлять поведением с помощью:
<script setup>
defineOptions({
inheritAttrs: false,
});
</script>
Fragment в Vue vs React Fragment
| Характеристика | Vue 3 Fragment | React Fragment |
|---|---|---|
| Синтаксис | Неявный (тег не нужен) | Явный (<> или <Fragment>) |
| Обработка ключей | Обычные правила vnode/key в списках | Key поддерживается на <Fragment key=...> |
| Проброс атрибутов | Используйте $attrs вручную при множественных корнях | Нет прямых атрибутов fragment |
4. Suspense
Что такое Suspense?
Suspense — встроенный компонент для состояний загрузки асинхронных зависимостей.
Он рендерит резервный UI, пока разрешается асинхронный компонент/setup.
Базовое использование
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>Загрузка...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(() =>
import('./AsyncComponent.vue')
);
</script>
Типичные случаи использования
- Загрузка асинхронных компонентов
- Асинхронные требования данных в
setup() - Скелетонные UI на уровне маршрутов или секций
5. Множественные v-model
Множественные привязки
v-model
Vue 3 поддерживает множественные привязки v-model на одном компоненте.
Каждая привязка соответствует prop + событию update:propName.
Vue 2 vs Vue 3
Vue 2: один паттерн v-model на компонент.
<CustomInput v-model="value" />
Vue 3: множественные именованные привязки v-model.
<CustomForm
v-model:username="username"
v-model:email="email"
v-model:password="password"
/>
Пример реализации компонента
<!-- CustomForm.vue -->
<template>
<div>
<input
:value="username"
@input="$emit('update:username', $event.target.value)"
/>
<input
:value="email"
@input="$emit('update:email', $event.target.value)"
/>
<input
:value="password"
@input="$emit('update:password', $event.target.value)"
/>
</div>
</template>
<script setup>
defineProps(['username', 'email', 'password']);
defineEmits(['update:username', 'update:email', 'update:password']);
</script>
6. Частые вопросы на собеседованиях
Частые вопросы на собеседованиях
Вопрос 1: когда следует использовать Teleport?
Нажмите, чтобы увидеть ответ
Используйте Teleport, когда визуальный рендеринг должен выйти за пределы локальных ограничений DOM:
- Модальные диалоги для избежания проблем со stacking/overflow родителя
- Tooltip/popover, которые не должны обрезаться
- Глобальные уведомления, рендерящиеся в выделенном корневом контейнере
Не используйте Teleport для обычного содержимого в потоке.
Вопрос 2: преимущества Fragment
Нажмите, чтобы увидеть ответ
Преимущества:
- Меньше узлов-обёрток
- Лучшая семантическая структура
- Более простой CSS во многих раскладках
- Меньшая глубина DOM и накладные расходы
Вопрос 3: наследование атрибутов при множественных корнях
Нажмите, чтобы увидеть ответ
Для компонентов с множественными корнями attrs не наследуются автоматически на один корень.
Обрабатывайте их явно с помощью $attrs и при необходимости inheritAttrs: false.
<template>
<div v-bind="$attrs">Корень A</div>
<div>Корень B</div>
</template>
Вопрос 4: Fragment в Vue vs React
Нажмите, чтобы увидеть ответ
Vue использует неявное поведение fragment в шаблонах.
React требует явный синтаксис fragment (<>...</> или <Fragment>).
Вопрос 5: пример реализации Suspense
Нажмите, чтобы увидеть ответ
<template>
<Suspense>
<template #default>
<AsyncUserProfile :userId="userId" />
</template>
<template #fallback>
<div class="loading">
<Spinner />
<p>Загрузка профиля пользователя...</p>
</div>
</template>
</Suspense>
</template>
<script setup>
import { ref, defineAsyncComponent } from 'vue';
import Spinner from './Spinner.vue';
const userId = ref(1);
const AsyncUserProfile = defineAsyncComponent(() =>
import('./UserProfile.vue')
);
</script>
7. Лучшие практики
Лучшие практики
Рекомендуется
<!-- 1) Используйте Teleport для оверлеев -->
<Teleport to="body">
<Modal v-if="showModal" />
</Teleport>
<!-- 2) Сохраняйте семантические шаблоны с множественными корнями, где уместно -->
<template>
<header>...</header>
<main>...</main>
<footer>...</footer>
</template>
<!-- 3) Оборачивайте асинхронные части в Suspense -->
<Suspense>
<AsyncComponent />
<template #fallback>
<LoadingSpinner />
</template>
</Suspense>
<!-- 4) Используйте явные имена для множественных v-model -->
<CustomForm v-model:username="username" v-model:email="email" />
Избегайте
<!-- 1) Не злоупотребляйте Teleport для обычного содержимого -->
<Teleport to="body">
<div>Обычное содержимое</div>
</Teleport>
<!-- 2) Не используйте множественные корни только для стиля; сохраняйте логическую группировку -->
<template>
<h1>Заголовок</h1>
<p>Содержимое</p>
</template>
<!-- 3) Не игнорируйте обработку ошибок/загрузки для асинхронных компонентов -->
<Suspense>
<AsyncComponent />
</Suspense>
8. Итоги для собеседования
Итоги для собеседования
Быстрое запоминание
Ключевые возможности Vue 3:
- Composition API
- Teleport
- Fragment
- Suspense
- Множественные
v-model
Пример ответа
В: Какие основные возможности Vue 3?
Composition API для лучшей организации и переиспользования логики, Teleport для рендеринга оверлеев вне локальных DOM-контейнеров, Fragment для множественных корневых узлов, Suspense для состояний асинхронной загрузки, множественные привязки
v-model, а также улучшенная поддержка TypeScript и производительности.
В: Какой практический случай использования Teleport?
Рендеринг модального/оверлейного окна в
bodyдля избежания проблем с обрезкой и stacking, сохраняя при этом логику модала внутри исходного дерева компонентов.