Как реализовать Toast-уведомления в Vue
Реализация toast-уведомлений во Vue 3 через кастомный composable или Vue Toastification, с доступной разметкой и паттернами Composition API.
Вам нужно показывать пользователям обратную связь — сохранённую форму, неудачный API-запрос, успешную загрузку. Toast-уведомления решают эту задачу элегантно: краткие сообщения, которые появляются, передают информацию и исчезают, не нарушая рабочий процесс.
Это руководство охватывает два подхода к toast-уведомлениям в Vue 3: создание собственной системы на основе composable и интеграцию проверенных библиотек, таких как Vue Toastification или Notivue. Оба подхода используют современные паттерны Composition API с синтаксисом <script setup>.
Ключевые выводы
- Собственная toast-система в Vue 3 требует всего три компонента: общее реактивное состояние, composable-функцию и контейнерный компонент на основе Teleport.
- Сторонние библиотеки, такие как Vue Toastification и Notivue, из коробки обрабатывают управление очередью, анимации, доступность и темизацию.
- В Nuxt 3 toast-уведомления требуют выполнения только на клиенте — используйте обёртки
<ClientOnly>или суффиксы.client.tsдля плагинов. - Доступные toast-уведомления используют
aria-liveрегионы, кнопки закрытия с понятными метками и учитывают настройкиprefers-reduced-motion.
Создание собственной Toast-системы с помощью Vue Composables
Минимальная система уведомлений на Vue composable требует три компонента: реактивное состояние, composable-функцию и отрендеренный компонент с использованием Teleport.
Toast Composable
Создайте общее реактивное хранилище, к которому может получить доступ любой компонент:
// composables/useToast.ts
import { ref, readonly } from 'vue'
interface Toast {
id: number
message: string
type: 'success' | 'error' | 'info' | 'warning'
}
const toasts = ref<Toast[]>([])
let id = 0
export function useToast() {
const add = (message: string, type: Toast['type'] = 'info', duration = 3000) => {
const toast = { id: ++id, message, type }
toasts.value.push(toast)
if (duration > 0) {
setTimeout(() => remove(toast.id), duration)
}
}
const remove = (toastId: number) => {
toasts.value = toasts.value.filter(t => t.id !== toastId)
}
return {
toasts: readonly(toasts),
success: (msg: string) => add(msg, 'success'),
error: (msg: string) => add(msg, 'error'),
info: (msg: string) => add(msg, 'info'),
warning: (msg: string) => add(msg, 'warning'),
remove
}
}
Реактивная ссылка toasts объявлена вне composable-функции, чтобы каждый вызов useToast() использовал один и тот же реактивный массив. Обёртка readonly предотвращает прямое изменение состояния потребителями — только add и remove могут модифицировать список.
Компонент контейнера Toast
Отрендерите toast-уведомления с помощью Teleport, чтобы разместить их вне дерева компонентов:
<!-- components/ToastContainer.vue -->
<script setup lang="ts">
import { useToast } from '@/composables/useToast'
const { toasts, remove } = useToast()
</script>
<template>
<Teleport to="body">
<div
class="toast-container"
role="region"
aria-live="polite"
aria-label="Notifications"
>
<div
v-for="toast in toasts"
:key="toast.id"
:class="['toast', `toast--${toast.type}`]"
role="status"
>
{{ toast.message }}
<button @click="remove(toast.id)" aria-label="Dismiss notification">×</button>
</div>
</div>
</Teleport>
</template>
Разместите <ToastContainer /> в вашем App.vue, затем вызывайте useToast() из любого компонента для запуска уведомлений.
Примечание о ARIA-ролях: Контейнер использует
aria-live="polite", чтобы программы чтения с экрана объявляли новые toast-уведомления, не прерывая пользователя. Отдельные toast-уведомления используютrole="status"вместоrole="alert", что правильно сочетается с вежливым live-регионом. Зарезервируйтеrole="alert"(который подразумеваетaria-live="assertive") для срочных сообщений об ошибках, требующих немедленного внимания.
Использование сторонних библиотек
Для production-приложений поддерживаемые библиотеки обрабатывают граничные случаи, которые вам иначе пришлось бы создавать самостоятельно: управление очередью, анимации, доступность и темизацию.
Настройка Vue Toastification
Установите и зарегистрируйте плагин:
// main.ts
import { createApp } from 'vue'
import Toast from 'vue-toastification'
import 'vue-toastification/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(Toast, {
position: 'top-right',
timeout: 5000
})
app.mount('#app')
Используйте composable в компонентах:
<script setup>
import { useToast } from 'vue-toastification'
const toast = useToast()
const handleSave = async () => {
try {
await saveData()
toast.success('Changes saved')
} catch {
toast.error('Save failed')
}
}
</script>
Discover how at OpenReplay.com.
Альтернатива Notivue
Notivue предлагает похожий composable API с дополнительными опциями кастомизации и встроенными функциями доступности.
Toast-уведомления в Nuxt 3
Toast-уведомления в Nuxt 3 должны запускаться в клиентских контекстах (например, в обработчиках событий или onMounted), потому что они манипулируют DOM и не должны выполняться во время серверного рендеринга:
<!-- app.vue -->
<template>
<NuxtPage />
<ClientOnly>
<ToastContainer />
</ClientOnly>
</template>
Если вы используете Nuxt UI, он предоставляет собственный composable useToast — дополнительная библиотека не нужна. Для других настроек создайте плагин Nuxt:
// plugins/toast.client.ts
import Toast from 'vue-toastification'
import 'vue-toastification/dist/index.css'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(Toast)
})
Суффикс .client.ts гарантирует, что плагин выполняется только в браузере.
Соображения доступности
Toast-уведомления должны быть доступны для пользователей программ чтения с экрана:
- Используйте
aria-live="polite"на контейнере toast-уведомлений, чтобы новые сообщения объявлялись без прерывания - Предоставьте кнопки закрытия с описательными атрибутами
aria-label - Учитывайте
prefers-reduced-motion, отключая анимации при запросе - Избегайте автоматического закрытия сообщений об ошибках — пользователям нужно время, чтобы прочитать их и отреагировать
@media (prefers-reduced-motion: reduce) {
.toast {
animation: none
}
}
Заключение
Создавайте собственную toast-систему, когда вам нужен минимальный размер бандла или очень специфичное поведение. Используйте Vue Toastification или Notivue, когда хотите проверенную доступность, анимации и опции конфигурации без накладных расходов на обслуживание.
Оба подхода работают в Vue 3 и Nuxt 3. Паттерн composable делает ваш код тестируемым, а компоненты — независимыми от логики уведомлений. Начните с собственного composable, чтобы понять механику, затем оцените, лучше ли библиотека служит долгосрочным потребностям вашего проекта.
Часто задаваемые вопросы
Могу ли я использовать собственный toast composable в нескольких компонентах Vue без prop drilling?
Да. Поскольку реактивная ссылка toasts объявлена вне функции useToast на уровне модуля, каждый компонент, вызывающий useToast, использует одно и то же реактивное состояние. Это означает, что любой компонент может запускать или закрывать toast-уведомления без передачи props или испускания событий через родительские компоненты.
Зачем использовать Teleport для контейнера toast вместо встроенного рендеринга?
Teleport перемещает DOM-узлы toast в элемент body, за пределы иерархии компонентов. Это предотвращает обрезку или скрытие ваших toast-уведомлений родительским CSS, таким как overflow hidden или контексты z-index stacking. Это также обеспечивает согласованное позиционирование toast независимо от того, где монтируется компонент контейнера.
Как предотвратить автоматическое закрытие toast-уведомлений для сообщений об ошибках?
В собственном composable передайте ноль или отрицательное число в качестве аргумента duration, чтобы пропустить вызов setTimeout. В Vue Toastification установите timeout в false для конкретных toast-уведомлений. Сообщения об ошибках должны сохраняться до тех пор, пока пользователь не закроет их вручную, чтобы у них было достаточно времени для чтения и реагирования.
Нужно ли по-другому обрабатывать SSR при использовании toast-уведомлений в Nuxt 3?
Да. Toast-уведомления манипулируют DOM, который недоступен во время серверного рендеринга. Оберните ваш контейнер toast в компонент ClientOnly или зарегистрируйте вашу toast-библиотеку как клиентский плагин Nuxt, используя суффикс файла .client.ts. Это гарантирует, что логика toast выполняется только в браузере.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k