Плагины во Vue 3
Создание и использование плагинов Vue — app.use(), provide/inject, директивы, миксины и реальные примеры: toast-плагин, i18n, аналитика.
Что такое плагин
Плагин — объект с методом install() или функция, которая вызывается при app.use(). Плагины добавляют глобальную функциональность: компоненты, директивы, provide/inject, глобальные свойства.
Базовый плагин
Объект с install
// plugins/myPlugin.ts
import type { App } from 'vue'
export default {
install(app: App, options?: { prefix?: string }) {
const prefix = options?.prefix ?? 'App'
app.provide('appName', prefix)
app.config.globalProperties.$appName = prefix
},
}
Функция
export default function myPlugin(app: App, options?: { prefix?: string }) {
app.provide('appName', options?.prefix ?? 'App')
}
Подключение
import myPlugin from './plugins/myPlugin'
app.use(myPlugin, { prefix: 'FrontSkill' })
Что может делать плагин
- Регистрировать глобальные компоненты
- Регистрировать директивы
- Предоставлять данные через
provide - Добавлять глобальные свойства
- Настраивать
app.config - Использовать
onUnmounted/onScopeDispose
Примеры плагинов
Toast-плагин
// plugins/toast.ts
import type { App } from 'vue'
import { ref } from 'vue'
import ToastContainer from '@/components/ToastContainer.vue'
interface Toast {
id: number
message: string
type: 'success' | 'error' | 'info'
duration: number
}
export function createToastPlugin() {
const toasts = ref<Toast[]>([])
let nextId = 0
function add(message: string, type: Toast['type'] = 'info', duration = 3000) {
const id = nextId++
toasts.value.push({ id, message, type, duration })
setTimeout(() => {
toasts.value = toasts.value.filter(t => t.id !== id)
}, duration)
}
return {
install(app: App) {
app.provide('toast', { add })
app.component('ToastContainer', ToastContainer)
},
toasts,
}
}
// main.ts
import { createToastPlugin } from './plugins/toast'
const toast = createToastPlugin()
app.use(toast)
Использование в компоненте:
const toast = inject('toast') as { add: (message: string, type?: string) => void }
toast.add('Сохранено!', 'success')
toast.add('Ошибка', 'error')
i18n-плагин
// plugins/i18n.ts
import type { App } from 'vue'
import { ref, computed } from 'vue'
export function createI18n(messages: Record<string, Record<string, string>>) {
const locale = ref('ru')
function t(key: string): string {
return messages[locale.value]?.[key] ?? key
}
function setLocale(newLocale: string) {
locale.value = newLocale
}
return {
install(app: App) {
app.provide('i18n', { t, locale, setLocale })
},
}
}
// main.ts
import { createI18n } from './plugins/i18n'
const i18n = createI18n({
ru: {
greeting: 'Привет',
logout: 'Выйти',
},
en: {
greeting: 'Hello',
logout: 'Log out',
},
})
app.use(i18n)
Analytics-плагин
// plugins/analytics.ts
import type { App } from 'vue'
import { ref } from 'vue'
interface AnalyticsOptions {
trackingId: string
debug?: boolean
}
export default {
install(app: App, options: AnalyticsOptions) {
const enabled = ref(true)
function track(event: string, data?: Record<string, unknown>) {
if (!enabled.value) return
if (options.debug) {
console.log(`[Analytics] ${event}`, data)
}
// Отправка в Google Analytics / Yandex Metrica / etc.
if (typeof window !== 'undefined' && (window as any).gtag) {
(window as any).gtag('event', event, data)
}
}
function disable() {
enabled.value = false
}
app.provide('analytics', { track, disable })
},
}
Плагин с директивой
// plugins/clickOutside.ts
import type { App, Directive, DirectiveBinding } from 'vue'
const vClickOutside: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding<(...args: any[]) => void>) {
function handleClick(event: Event) {
if (!(el === event.target || el.contains(event.target as Node))) {
binding.value(event)
}
}
document.addEventListener('click', handleClick)
;(el as any).__clickOutside = = handleClick
},
unmounted(el: HTMLElement) {
document.removeEventListener('click', (el as any).__clickOutside)
},
}
export default {
install(app: App) {
app.directive('click-outside', vClickOutside)
},
}
<template>
<div v-click-outside="close" class="dropdown">
<ul>...</ul>
</div>
</template>
Плагин с глобальными компонентами
// plugins/ui.ts
import type { App } from 'vue'
import BaseButton from '@/components/BaseButton.vue'
import BaseInput from '@/components/BaseInput.vue'
import BaseModal from '@/components/BaseModal.vue'
import BaseCard from '@/components/BaseCard.vue'
export default {
install(app: App) {
app.component('BaseButton', BaseButton)
app.component('BaseInput', BaseInput)
app.component('BaseModal', BaseModal)
app.component('BaseCard', BaseCard)
},
}
// main.ts
import UIPlugin from './plugins/ui'
app.use(UIPlugin)
Теперь эти компоненты доступны везде без импортов.
Типизация глобальных свойств
Когда плагин добавляет свойства в globalProperties:
declare module 'vue' {
interface ComponentCustomProperties {
$appName: string
$toast: {
add: (message: string, type?: string) => void
}
}
}
provide/inject с Symbol-ключами
Лучше использовать Symbol, чтобы избежать коллизий:
// plugins/toast.ts
export const ToastKey = Symbol('toast')
// В плагине
app.provide(ToastKey, { add })
// В компоненте
const toast = inject(ToastKey)!
С типизацией:
export const ToastKey: InjectionKey<{
add: (message: string, type?: 'success' | 'error' | 'info') => void
}> = Symbol('toast')
Порядок регистрации
Плагины применяются в порядке вызова app.use():
app.use(pinia) // Сначала Pinia — хранилище
app.use(router) // Потом Router — маршрутизация
app.use(i18n) // Потом i18n — переводы
app.use(toast) // Потом Toast — уведомления
Pinia должна быть подключена до stores, которые используются в guards роутера.
Итог
Плагины — способ добавить глобальную функциональность в Vue-приложение. Используйте provide/inject вместо globalProperties для лучшей типизации. Symbol-ключи защищают от коллизий. Плагины могут регистрировать компоненты, директивы и composables. Типизируйте глобальные свойства через ComponentCustomProperties.