Kurs Vue.js · Moduł 14: Dyrektywy i pluginy
Tworzenie użytecznych pluginów
W tej lekcji3
W NOVA LAB każdy system stacji jest niezależnym modułem. Zobaczmy, jak tworzyć pluginy, które są naprawdę przydatne: toast notifications i i18n (internacjonalizacja).
Toast Plugin - powiadomienia
Toast to krótkie powiadomienie wyświetlane tymczasowo. Plugin dostarczy metodę show() dostępną w całej aplikacji:
1const ToastPlugin = {
2 install(app, options = {}) {
3 const duration = options.duration || 3000
4 const position = options.position || 'top-right'
5
6 const toasts = ref([])
7 let toastId = 0
8
9 const toast = {
10 show(message, type = 'info') {
11 const id = toastId++
12 toasts.value.push({ id, message, type })
13 setTimeout(() => {
14 toasts.value = toasts.value.filter(t => t.id !== id)
15 }, duration)
16 },
17 success(msg) { this.show(msg, 'success') },
18 error(msg) { this.show(msg, 'error') },
19 warning(msg) { this.show(msg, 'warning') },
20 list: toasts
21 }
22
23 app.provide('toast', toast)
24 }
25}Użycie w komponencie:
1<script setup>
2import { inject } from 'vue'
3
4const toast = inject('toast')
5
6function onSave() {
7 toast.success('Dane zapisane!')
8}
9
10function onError() {
11 toast.error('Błąd połączenia z satelitą!')
12}
13</script>i18n Plugin - wielojęzyczność
Plugin do tłumaczenia tekstów na różne języki:
1const I18nPlugin = {
2 install(app, options) {
3 const locale = ref(options.defaultLocale || 'pl')
4 const messages = options.messages || {}
5
6 function t(key) {
7 const keys = key.split('.')
8 let result = messages[locale.value]
9 for (const k of keys) {
10 result = result?.[k]
11 }
12 return result || key
13 }
14
15 function setLocale(lang) {
16 if (messages[lang]) {
17 locale.value = lang
18 }
19 }
20
21 app.provide('i18n', {
22 t,
23 locale,
24 setLocale,
25 availableLocales: Object.keys(messages)
26 })
27
28 app.directive('t', (el, binding) => {
29 el.textContent = t(binding.value)
30 })
31 }
32}Użycie:
1app.use(I18nPlugin, {
2 defaultLocale: 'pl',
3 messages: {
4 pl: {
5 nav: { home: 'Strona główna', settings: 'Ustawienia' },
6 status: { online: 'Połączony', offline: 'Rozłączony' }
7 },
8 en: {
9 nav: { home: 'Home', settings: 'Settings' },
10 status: { online: 'Connected', offline: 'Disconnected' }
11 }
12 }
13})W komponencie:
1<script setup>
2import { inject } from 'vue'
3const { t, setLocale, locale } = inject('i18n')
4</script>
5
6<template>
7 <nav>
8 <a>{{ t('nav.home') }}</a>
9 <a>{{ t('nav.settings') }}</a>
10 </nav>
11 <p>Status: {{ t('status.online') }}</p>
12 <button @click="setLocale('en')">EN</button>
13 <button @click="setLocale('pl')">PL</button>
14</template>Wzorce dobrych pluginów
- Provide/Inject - preferuj nad globalProperties
- Opcje z domyślnymi wartościami - plugin musi działać bez konfiguracji
- Typowanie - eksportuj interfejsy dla TypeScript
- Czystka - jeżeli plugin modyfikuje DOM, zadbaj o cleanup
- Dokumentacja - opisz dostępne opcje i metody
Kod do tej lekcji: App.vue
1<script setup>
2import { inject, ref } from 'vue'
3
4const toast = inject('toast')
5const { t, locale, setLocale } = inject('i18n')
6
7function showSuccess() {
8 toast.success(t('toast.saved'))
9}
10function showError() {
11 toast.error(t('toast.connectionError'))
12}
13function showWarning() {
14 toast.warning(t('toast.lowOxygen'))
15}
16</script>
17
18<template>
19 <div class="nova-lab">
20 <h1>{{ t('title') }}</h1>
21
22 <section class="section">
23 <h2>Toast Plugin</h2>
24 <div class="btn-row">
25 <button @click="showSuccess" class="btn success">{{ t('toast.successBtn') }}</button>
26 <button @click="showError" class="btn error">{{ t('toast.errorBtn') }}</button>
27 <button @click="showWarning" class="btn warn">{{ t('toast.warnBtn') }}</button>
28 </div>
29 </section>
30
31 <section class="section">
32 <h2>i18n Plugin</h2>
33 <p>{{ t('status.label') }}: {{ t('status.online') }}</p>
34 <p>{{ t('crew.label') }}: {{ t('crew.count') }}</p>
35 <div class="btn-row" style="margin-top: 10px">
36 <button
37 @click="setLocale('pl')"
38 :class="['btn', { active: locale === 'pl' }]"
39 >PL</button>
40 <button
41 @click="setLocale('en')"
42 :class="['btn', { active: locale === 'en' }]"
43 >EN</button>
44 </div>
45 <p class="hint">Przelacz jezyk i zobacz jak zmieniaja sie teksty</p>
46 </section>
47
48 <!-- Toast container -->
49 <div class="toast-container">
50 <div
51 v-for="t in toast.list.value"
52 :key="t.id"
53 :class="['toast', t.type]"
54 >
55 {{ t.message }}
56 </div>
57 </div>
58 </div>
59</template>
60
61<style scoped>
62.nova-lab {
63 background: #0a0e27;
64 color: #c0c0c0;
65 min-height: 100vh;
66 padding: 20px;
67 font-family: monospace;
68}
69h1 { color: #00ff88; margin-bottom: 20px; }
70h2 { color: #00b4d8; font-size: 16px; margin-bottom: 10px; }
71.section {
72 background: rgba(0,180,216,0.05);
73 border: 1px solid rgba(0,180,216,0.2);
74 padding: 15px;
75 border-radius: 8px;
76 margin-bottom: 15px;
77}
78.btn-row { display: flex; gap: 10px; }
79.btn {
80 background: rgba(0,255,136,0.15);
81 color: #00ff88;
82 border: 1px solid #00ff88;
83 padding: 8px 16px;
84 border-radius: 6px;
85 cursor: pointer;
86 font-family: monospace;
87}
88.btn.active { background: #00ff88; color: #0a0e27; }
89.btn.success { color: #2ecc71; border-color: #2ecc71; }
90.btn.error { color: #e74c3c; border-color: #e74c3c; }
91.btn.warn { color: #f39c12; border-color: #f39c12; }
92.toast-container {
93 position: fixed; top: 20px; right: 20px;
94 display: flex; flex-direction: column; gap: 8px;
95 z-index: 1000;
96}
97.toast {
98 padding: 12px 20px;
99 border-radius: 8px;
100 font-family: monospace;
101 font-size: 14px;
102 animation: slideIn 0.3s ease;
103}
104.toast.success { background: rgba(46,204,113,0.9); color: white; }
105.toast.error { background: rgba(231,76,60,0.9); color: white; }
106.toast.warning { background: rgba(243,156,18,0.9); color: white; }
107.toast.info { background: rgba(0,180,216,0.9); color: white; }
108@keyframes slideIn {
109 from { transform: translateX(100%); opacity: 0; }
110 to { transform: translateX(0); opacity: 1; }
111}
112.hint { color: #666; font-size: 12px; margin-top: 8px; }
113</style>Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Jak zrealizować automatyczne znikanie toastów po 3 sekundach?
2. Co robi funkcja t('nav.home') w pluginie i18n?
Zadania praktyczne w grze
- Układanie w pionie
Ułóż kroki działania metody toast.show() w poprawnej kolejności:
- Edytor kodu
Zaimplementuj ToastPlugin z reaktywną tablicą toastów i metodami show/success/error.
- Klikanie w kolejności
Ułóż poprawną składnię app.provide w pluginie:
- Układanie w poziomie
Ułóż poprawne wywołanie inject do pobrania toastu: