Kurs Vue.js · Moduł 14: Dyrektywy i pluginy

Tworzenie użytecznych pluginów

2 min czytania
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

  1. Provide/Inject - preferuj nad globalProperties
  2. Opcje z domyślnymi wartościami - plugin musi działać bez konfiguracji
  3. Typowanie - eksportuj interfejsy dla TypeScript
  4. Czystka - jeżeli plugin modyfikuje DOM, zadbaj o cleanup
  5. 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. 1. Jak zrealizować automatyczne znikanie toastów po 3 sekundach?

  2. 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:

Przydatne artykuły