Андрей Шалагин
Полный гайд по компоненту 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-обновлений, если сообщения добавляются асинхронно.