Back

Как реализовать Toast-уведомления в Vue

Как реализовать Toast-уведомления в Vue

Вам нужно показывать пользователям обратную связь — сохранённую форму, неудачный 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>

Альтернатива 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, чтобы понять механику, затем оцените, лучше ли библиотека служит долгосрочным потребностям вашего проекта.

Часто задаваемые вопросы

Да. Поскольку реактивная ссылка toasts объявлена вне функции useToast на уровне модуля, каждый компонент, вызывающий useToast, использует одно и то же реактивное состояние. Это означает, что любой компонент может запускать или закрывать toast-уведомления без передачи props или испускания событий через родительские компоненты.

Teleport перемещает DOM-узлы toast в элемент body, за пределы иерархии компонентов. Это предотвращает обрезку или скрытие ваших toast-уведомлений родительским CSS, таким как overflow hidden или контексты z-index stacking. Это также обеспечивает согласованное позиционирование toast независимо от того, где монтируется компонент контейнера.

В собственном composable передайте ноль или отрицательное число в качестве аргумента duration, чтобы пропустить вызов setTimeout. В Vue Toastification установите timeout в false для конкретных toast-уведомлений. Сообщения об ошибках должны сохраняться до тех пор, пока пользователь не закроет их вручную, чтобы у них было достаточно времени для чтения и реагирования.

Да. 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. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay