логотип PurpleSchool
логотип PurpleSchool

Полный гайд по компоненту messages во Vuejs

Автор

Андрей Шалагин

Введение

Компонент messages часто используется во Vue.js для вывода уведомлений, ошибок, алертов и других пользовательских сообщений. Такой компонент помогает пользователю понять, что происходит в приложении, предоставляет обратную связь и делает интерфейс интерактивнее. В ecosystem Vue нет встроенного компонента messages, подобно notifications в некоторых других фреймворках — но существует устоявшийся подход к созданию и интеграции такого рода решений.

В этом гайдe вы узнаете, как проектируют и реализуют компонент messages на Vue.js, почему это удобно, как расширять его функциональность и встраивать в разноплановые проекты. Я покажу вам реализацию на разных уровнях сложности, обработку очереди сообщений, доступ через централизованный store и другие часто используемые техники.


Что такое компонент messages

Назначение и основные задачи

Компонент messages — это панель с оповещениями, всплывающие notification-окна или группе видимых сообщений, управляемых из разных частей приложения. Эти сообщения могут быть различного типа:

  • успех (success)
  • ошибка (error)
  • предупреждение (warning)
  • информационные сообщения (info)
  • кастомные (например, прогресс или напоминания)

Чтобы реализовать такую систему, необходимо учесть несколько факторов:

  • Гибкость — сообщения могут приходить из любого места приложения.
  • Видимость и сокрытие — нужно управлять появлением и исчезновением сообщений.
  • Типизация сообщений — разметка и стиль должны отличаться в зависимости от типа сообщения.
  • Возможность стекирования — поддержка нескольких одновременных сообщений.
  • Закрытие вручную и по таймеру — для UX важно и ручное, и автоматическое скрытие.

Где применяют компонент messages

Вы наверняка видели аналоги во многих популярных приложениях: баннеры с ошибками на верхушке сайта, уведомления о загрузке данных, подтверждении действий и т.д. Без такого механизма пользователь может не получить своевременной обратной связи или останется без информации о внутренних процессах.


Базовая реализация компонента messages во Vue.js

Давайте рассмотрим, как можно создать свой компонент messages с нуля, без сторонних библиотек.

Определение структуры компонента

Простейший вариант — компонент, который хранит массив сообщений, отображает их и умеет удалять по событию.

<template>
  <div class="messages">
    <div
      v-for="msg in messages"
      :key="msg.id"
      :class="['message', msg.type]"
    >
      <span>{{ msg.text }}</span>
      <button @click="removeMessage(msg.id)">×</button>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Messages',
  data() {
    return {
      messages: []
    }
  },
  methods: {
    // Добавление нового сообщения
    addMessage(message) {
      // Сообщение должно иметь уникальный id, тип и текст
      this.messages.push({
        ...message,
        id: Date.now() + Math.random()
      });
    },
    // Удаление сообщения по id
    removeMessage(id) {
      this.messages = this.messages.filter(msg => msg.id !== id);
    }
  }
}
</script>

<style>
.messages { position: fixed; top: 20px; right: 20px; z-index: 1000; }
.message { padding: 12px 18px; margin-bottom: 10px; border-radius: 4px; color: #fff; }
.message.success { background: #4caf50; }
.message.error { background: #f44336; }
.message.info { background: #2196f3; }
.message.warning { background: #ff9800; }
button { margin-left: 15px; cursor: pointer; background: none; border: none; color: #fff; font-weight: bold; }
</style>

Объяснения к коду

  • Мы используем массив messages для хранения всех активных сообщений.
  • Каждое сообщение — это объект с уникальным id, type (для стилизации и семантики) и text.
  • Кнопка «×» предназначена для удаления сообщения вручную.
  • Структура компонента позволяет легко расширять или модифицировать логику отображения.

Как вызывать сообщения из других компонентов

Чтобы добавить сообщение из любого места приложения, можно воспользоваться доступностью компонента через $refs, например так:

<!-- В родительском компоненте -->
<Messages ref="messagesComp" />
// В другом методе внутри вашего приложения
this.$refs.messagesComp.addMessage({ text: 'Готово!', type: 'success' })

Проблемы такого подхода

  • Такой вызов доступен только родительским компонентам, доступ к $refs извне затруднен.
  • Сложно масштабировать, если оповещения необходимо показывать из Vuex, асинхронных action-ов и др.

Централизованное управление сообщениями через EventBus

Для повышения гибкости стоит организовать EventBus — глобальный канал обмена событиями.

Создание EventBus

// eventBus.js
import Vue from 'vue'
export const EventBus = new Vue();

Использование EventBus в компоненте

import { EventBus } from './eventBus.js'

export default {
  // остальные опции компонента
  created() {
    // Слушаем события 'add-message'
    EventBus.$on('add-message', this.addMessage);
  },
  destroyed() {
    EventBus.$off('add-message', this.addMessage);
  },
  methods: {
    addMessage(message) {
      // Добавляем сообщение в список
      // Всё аналогично предыдущему примеру
    }
  }
}

Теперь можно отправлять сообщение из любого места приложения:

import { EventBus } from './eventBus.js'

// Покажем сообщение с текстом и типом error
EventBus.$emit('add-message', { text: 'Что-то пошло не так', type: 'error' });

Почему это удобно

  • Любой компонент, action или модуль может вызвать показ сообщения без тесной привязки к структуре приложения.
  • Принцип "единого входа" — все сообщения централизованно проходят через один поток событий.

Продвинутые возможности компонента messages

Автоматическое скрытие сообщений (auto close)

Часто уведомления должны исчезать через заданное время. Для этого добавим время жизни (timeout) для каждого сообщения:

addMessage(message) {
  const id = Date.now() + Math.random()
  const msg = { ...message, id }
  this.messages.push(msg)
  // Если установлено autoClose или timeout — удаляем по таймеру
  if (message.timeout !== 0) {
    setTimeout(() => {
      this.removeMessage(id)
    }, message.timeout || 4000); // по умолчанию 4 секунды
  }
}

Теперь вы можете вызвать:

EventBus.$emit('add-message', {
  text: 'Сохранено успешно!',
  type: 'success',
  timeout: 5000 // 5 секунд
})

Ограничение количества отображаемых сообщений

Часто не нужно перегружать интерфейс большим числом карточек сообщений. Можно ограничить их, например, до 3-х:

addMessage(message) {
  if (this.messages.length >= 3) {
    // Удаляем самое старое сообщение
    this.messages.shift()
  }
  // Дальше обычная логика...
}

Поддержка разных видов контента

Чтобы сообщения могли не только текст отображать, но и, скажем, html или компоненты, используйте слот:

<template>
  <div v-for="msg in messages" :key="msg.id" :class="['message', msg.type]">
    <slot :message="msg">
      <!-- default fallback -->
      <span>{{ msg.text }}</span>
    </slot>
    <button @click="removeMessage(msg.id)">×</button>
  </div>
</template>

Теперь в родителе вы можете оформить вывод сообщения произвольно:

<Messages>
  <template v-slot:default="slotProps">
    <strong v-if="slotProps.message.type === 'error'">Ошибка!</strong>
    <span>{{ slotProps.message.text }}</span>
  </template>
</Messages>

Использование Vuex для хранения сообщений

В больших приложениях для глобального реагирования лучше поместить логику сообщений в Store.

Создание модуля для сообщений

// store/messages.js
export default {
  namespaced: true,
  state: () => ({
    messages: []
  }),
  mutations: {
    ADD_MESSAGE(state, msg) {
      state.messages.push(msg)
    },
    REMOVE_MESSAGE(state, id) {
      state.messages = state.messages.filter(msg => msg.id !== id)
    }
  },
  actions: {
    addMessage({ commit }, message) {
      const id = Date.now() + Math.random()
      commit('ADD_MESSAGE', { ...message, id })
      // Удаляем сообщение по таймеру, если нужно
      if (message.timeout !== 0) {
        setTimeout(() => commit('REMOVE_MESSAGE', id), message.timeout || 4000)
      }
    },
    removeMessage({ commit }, id) {
      commit('REMOVE_MESSAGE', id)
    }
  }
}

Компонент для вывода сообщений на основе Store

computed: {
  messages() {
    return this.$store.state.messages.messages
  }
},
methods: {
  removeMessage(id) {
    this.$store.dispatch('messages/removeMessage', id)
  }
}

В добавление сообщения теперь можно производить через action:

this.$store.dispatch('messages/addMessage', { text: 'Данные получены', type: 'success', timeout: 3000 });

Встраивание сторонних решений и библиотек

Если не хотите реализовывать все вручную, можно взять готовое уведомление-пакет. Наиболее популярны:

Давайте рассмотрим пример с vue-notification:

Установка и базовое использование

Установка:

npm install vue-notification

Подключение:

import Notifications from 'vue-notification'
Vue.use(Notifications)

В шаблоне:

<notifications group="foo" position="top right" />

Вызов уведомления:

this.$notify({
  group: 'foo',
  title: 'Ошибка',
  text: 'Произошла ошибка загрузки данных!'
});

Настройка внешнего вида

Vue-notification поддерживает настройку параметров кастомизации: стили, позиционирование, очередность.

  • В отличие от самописных решений, интеграция с SSR и мобильными браузерами здесь уже реализована.
  • Но настраиваемость (типизация сообщений, собственные компоненты в контенте) может быть ограничена рамками библиотеки.

Полезные паттерны расширения messages

Локализованные сообщения и мультиязычность

Для мультиязычности интегрируйте компонент с i18n-провайдером:

import { i18n } from '@/i18n'

this.$store.dispatch('messages/addMessage', {
  text: i18n.t('msg.saved'),
  type: 'success'
});

Queue сообщений

Иногда сообщения имеют continue или replace-логику, например:

  • Новое сообщение заменяет текущее (режим single)
  • Все сообщения выстраиваются в очередь и показываются поочередно

Это реализуется через логику очереди:

// После показа сообщения удаляем его из очереди, добавляем следующее

Разделение сообщений по группам

Могут понадобиться отдельные стеки сообщений для разных частей интерфейса:

// Сообщения с group: 'main', 'auth', 'checkout'
// Компонент фильтрует отображаемые сообщения по group

Типичные ошибки и способы их избежать

  • Отсутствие уникального ключа — Используйте строго уникальный id для сообщений. Не полагайтесь на индекс массива.
  • Потеря событий при hot reload — Если используете EventBus, следите за отпиской слушателей при destroy компонента.
  • Утечка памяти из-за таймеров — Не забывайте очищать setTimeout на unmount, если их слишком много.
  • Переключаемость с SSR — Сторонние решения поддерживают SSR частично, пишите компонент messages с учетом возможности рендера на сервере.

Интеграция с формами и запросами

Удобно показывать сообщения об ошибках при валидации форм:

if (!this.user.email) {
  this.$store.dispatch('messages/addMessage', {
    type: 'error',
    text: 'Email обязателен'
  });
}

Или при сетевых ошибках:

axios.get('/api/data')
  .catch(() => {
    EventBus.$emit('add-message', { text: 'Ошибка сети', type: 'error' })
  })

Интеграция в единую точку входа приложения

Реализуйте компонент messages как синглтон на root-уровне (например, в App.vue), чтобы гарантировать его присутствие в любой части приложения. Это упрощает перемещение вызовов сообщений из Vuex action, middleware роутеров и утилит.


Масштабирование и тесты компонентa messages

  • Используйте snapshot-тесты для визуальной регрессии UI сообщений.
  • Для unit-тестов можно мокать EventBus и тестировать логику добавления/удаления сообщений.
  • В большом приложении разделите core-логику (модель) и визуал, чтобы можно было интегрировать новые стили или варианты показа без переписывания всего компонента.

Готовый пример plug-n-play компонента

Выведу вам удобную структуру из всех рассмотренных паттернов в мини-компоненте (Vue 2.x):

<template>
  <div class="messages">
    <div
      v-for="msg in messages"
      :key="msg.id"
      :class="['message', msg.type]"
      @mouseenter="pauseTimeout(msg.id)"
      @mouseleave="resumeTimeout(msg.id)"
    >
      <slot :message="msg">
        <span>{{ msg.text }}</span>
      </slot>
      <button @click="remove(msg.id)">×</button>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Messages',
  data() {
    return {
      messages: [],
      timeouts: {}
    }
  },
  methods: {
    add({ text, type = 'info', timeout = 4000 }) {
      const id = Date.now() + Math.random()
      this.messages.push({ id, text, type })
      this.timeouts[id] = setTimeout(() => this.remove(id), timeout)
    },
    remove(id) {
      this.messages = this.messages.filter(msg => msg.id !== id)
      clearTimeout(this.timeouts[id])
      delete this.timeouts[id]
    },
    pauseTimeout(id) {
      // Если хотите добавить паузу на таймер удаления при наведении мыши
      clearTimeout(this.timeouts[id])
    },
    resumeTimeout(id) {
      // Здесь можно реализовать продолжение таймера
    }
  }
}
</script>

Использование:

<Messages ref="messages" />
// В любом месте приложения
this.$refs.messages.add({ type: 'success', text: 'Успех', timeout: 3000 })

Заключение

Компонент messages во Vue.js — это неотъемлемая часть современного пользовательского интерфейса. Вы сами можете гибко реализовать его в своем проекте, используя как EventBus, Vuex или сторонние библиотеки, так и простые самописные решения. Такой компонент легко масштабировать, настраивать под стилистику проекта и расширять под новые сценарии.


Частозадаваемые технические вопросы по теме статьи и ответы на них

1. Как сделать, чтобы сообщения появлялись по центру экрана, а не в правом верхнем углу?

Добавьте стиль для позиционирования всего контейнера сообщений по центру: css .messages { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); } Это позволит сообщениям появляться в центре экрана.

2. Как отображать разные иконки для типов сообщений?

Добавьте для каждого типа сообщения соответствующий элемент с иконкой: ```vue

``` Можно использовать SVG или иконки из популярных библиотек, например FontAwesome.

3. Как прокидывать сообщения снаружи приложения, например из функции, не находящейся в Vue-компоненте?

Создайте глобальный модуль или функцию, которая импортирует EventBus или store, и вызывайте методы добавления сообщений оттуда.

4. Почему мои сообщения пропадают слишком быстро или не исчезают вообще?

Проверьте правильно ли вы устанавливаете и очищаете таймеры (setTimeout / clearTimeout). Указывайте явно timeout: 0 если сообщение не должно исчезать автоматически.

5. Как тестировать компонент messages во Vue Unit-тестах?

Мокируйте EventBus или Vuex, имитируйте отправку сообщений и проверьте их появление и удаление через методы компонента. Используйте nextTick для ожидания DOM-обновлений, если сообщения добавляются асинхронно.

Стрелочка влевоКак использовать meta для улучшения SEO на Vue5 правил использования Inertia с Vue и LaravelСтрелочка вправо

Все гайды по Vue

Руководство по валидации форм во Vue.jsИнтеграция Tiptap для создания редакторов на VueРабота с таблицами во Vue через TanStackИнструкция по установке и компонентам Vue sliderУправление пакетами Vue js с помощью npmУправление пакетами и node modules в Vue проектахКак использовать meta для улучшения SEO на VueПолный гайд по компоненту messages во Vuejs5 правил использования Inertia с Vue и LaravelРабота с модулями и пакетами в VueИнструкция по работе с grid на VueGithub для Vue проектов - подробная инструкция по хранению и совместной работеНастройка ESLint для Vue проектов и поддержка качества кодаОбработка ошибок и отладка в Vue.jsИспользование Vue Devtools для отладки и мониторинга приложенийРабота с конфигурационными файлами и скриптами VueСоздание и настройка проектов Vue с помощью Vue CLI3 способа интеграции Chart.js с Vue для создания графиковРабота с Canvas во VueИнструкция по реализации календаря во VueРабота с Ant Design Vue для создания UI на Vue
Обзор и использование утилит Vue для удобной разработкиРабота с обновлениями компонента и жизненным циклом updateРазрешение конфликтов и ошибок с помощью Vue resolveИспользование query-параметров и их обработка в маршрутах VueЗагрузка и управление состоянием загрузки в VueИспользование библиотек Vue для расширения функционалаРабота с JSON данными в приложениях VueКак работать с экземплярами компонента Instance во VueПолучение данных и API-запросы во Vue.jsЭкспорт и импорт данных и компонентов в VueОбработка событий и их передача между компонентами VuejsГайд по defineEmits на Vue 3Понимание core функционала Vue и его применениеПонимание и применение Composition API в Vue 3Понимание и работа с компилятором VueКогда и как использовать $emit и call во VueВзаимодействие с внешними API через Axios в Vue
Веб приложения на Vue архитектура и лучшие практикиИспользование Vite для быстрого старта и сборки проектов на Vue 3Работа с URL и ссылками в приложениях на VueРабота с пользовательскими интерфейсами и UI библиотеками во VueОрганизация и структура исходных файлов в проектах VueИспользование Quasar Framework для разработки на Vue с готовыми UI-компонентамиОбзор популярных шаблонов и стартовых проектов на VueИнтеграция Vue с PHP для создания динамичных веб-приложенийКак организовать страницы и маршруты в проекте на VueNuxt JS и Vue 3 для SSR приложенийСоздание серверных приложений на Vue с помощью Nuxt jsИспользование Vue Native для разработки мобильных приложенийОрганизация и управление индексной страницей в проектах VueИспользование Docker для контейнеризации приложений на VueИнтеграция Vue.js с Django для создания полноценных веб-приложенийСоздание и работа с дистрибутивом build dist Vue приложенийРабота со стилями и CSS в Vue js для красивых интерфейсовСоздание и структурирование Vue.js приложенияКак исправить ошибку cannot find module vueНастройка и сборка проектов Vue с использованием современных инструментовИнтеграция Vue с Bitrix для корпоративных решенийРазработка административных панелей на Vue js
5 библиотек для создания tree view во VueИнтеграция Tailwind CSS с Vue для современных интерфейсовИнтеграция Vue с серверной частью и HTTPS настройкамиКак обрабатывать async операции с Promise во VueИнтеграция Node.js и Vue.js для разработки приложенийРуководство по интеграции Vue js в NET проектыПримеры использования JSX во VueГайд по импорту и регистрации компонентов на VueМногоязычные приложения на Vue с i18nИнтеграция FLIR данных с Vue5 примеров использования filter во Vue для упрощения разработки3 примера реализации drag-and-drop во Vue
Управление переменными и реактивными свойствами во VueИспользование v for и slot в VueПрименение v-bind для динамической привязки атрибутов в VueУправление пользователями и их данными в Vue приложенияхСоздание и использование UI Kit для Vue приложенийТипизация и использование TypeScript в VuejsИспользование шаблонов в Vue js для построения интерфейсовИспользование Swiper для создания слайдеров в VueРабота со стилями и стилизацией в VueСтруктура и особенности Single File Components SFC в VueРабота со SCSS в проектах на Vue для стилизацииРабота со скроллингом и прокруткой в Vue приложенияхПрименение script setup синтаксиса в Vue 3 для упрощения компонентовИспользование scoped стилей для изоляции CSS в компонентах Vue3 способа улучшить навигацию Vue с push()Обработка запросов и асинхронных операций в VueПонимание и использование provide inject для передачи данных между компонентамиПередача и использование props в Vue 3 для взаимодействия компонентовПередача данных между компонентами с помощью props в Vue jsУправление property и функциями во Vue.jsРабота со свойствами компонентов VueУправление параметрами и динамическими данными во VueРабота с lifecycle-хуком onMounted во VueОсновы работы с объектами в VueПонимание жизненного цикла компонента Vue js на примере mountedИспользование модальных окон modal в Vue приложенияхИспользование методов в компонентах Vue для обработки логикиИспользование метода map в Vue для обработки массивовИспользование хуков жизненного цикла Vue для управления состоянием компонентаРабота с ключами key в списках и компонентах VueОбработка пользовательского ввода в Vue.jsРабота с изображениями и их оптимизация в VueИспользование хуков жизненного цикла в VueОрганизация сеток и гридов для верстки интерфейсов на VueСоздание и управление формами в VueОрганизация файлов и структура проекта Vue.jsКомпоненты Vue создание передача данных события и emitРабота с динамическими компонентами и данными в Vue3 способа манипулирования DOM на VueРуководство по div во VueИспользование директив в Vue и их расширенные возможностиОсновы и применение директив в VueИспользование директив и их особенности на Vue с помощью defineИспользование компонентов datepicker в Vue для выбора датОрганизация циклов и итераций во VueКак работает компиляция Vue CoreСоздание и использование компонентов в Vue JSОбработка кликов и пользовательских событий в VueИспользование классов в Vue для организации кода и компонентовИспользование директивы checked для управления состоянием чекбоксов в VueГайд на checkbox компонент во VueОтображение данных в виде графиков с помощью Vue ChartСоздание и настройка кнопок в VueСоздание и настройка кнопок в Vue приложенияхРабота с lifecycle-хуками beforeCreate и beforeMount во VueИспользование массивов и методов их обработки в VueИспользование массивов и их обработка в Vue
Использование Vuetify для создания современных интерфейсов на VueИспользование transition во VueТестирование компонентов и приложений на VueРабота с teleport для управления DOM во VueПять шагов по настройке SSR в VuejsИспользование Shadcn UI компонентов с Vue для продвинутых интерфейсовИспользование router-link для навигации в Vue RouterКак использовать require в Vue для динамического импорта модулейРабота с динамическим рендерингом и виртуальным DOM на Vue.jsИспользование ref для управления ссылками и реактивностью в Vue 3Использование Vue Pro и его преимущества для профессиональной разработкиРуководство по nextTick для работы с DOMСоздание и использование компонентов с помощью Vue js и CУправление состоянием и реактивностью через inject и provideДинамическое обновление компонентов и данных на VueГлубокое изучение документации Vue и как эффективно её использоватьИспользование Crystal с Vue для разработкиИспользование вычисляемых свойств для динамического отображения данных на Vue jsОптимизация производительности и предупреждения в Vue
Открыть базу знаний