Cours Vue.js · Module 14: Directives et plugins

Créer des plugins utiles

3 min de lecture
Dans cette leçon3

Dans NOVA LAB, chaque système de la station est un module indépendant. Voyons comment créer des plugins réellement utiles : les notifications toast et l'i18n (internationalisation).

Toast Plugin - les notifications

Un toast est une courte notification affichée temporairement. Le plugin fournira une méthode show() disponible dans toute l'application :

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}

Utilisation dans un composant :

1<script setup>
2import { inject } from 'vue'
3
4const toast = inject('toast')
5
6function onSave() {
7  toast.success('Données enregistrées !')
8}
9
10function onError() {
11  toast.error('Erreur de connexion au satellite !')
12}
13</script>

i18n Plugin - support multilingue

Un plugin pour traduire des textes dans différentes langues :

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}

Utilisation :

1app.use(I18nPlugin, {
2  defaultLocale: 'fr',
3  messages: {
4    fr: {
5      nav: { home: 'Accueil', settings: 'Paramètres' },
6      status: { online: 'Connecté', offline: 'Déconnecté' }
7    },
8    en: {
9      nav: { home: 'Home', settings: 'Settings' },
10      status: { online: 'Connected', offline: 'Disconnected' }
11    }
12  }
13})

Dans un composant :

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('fr')">FR</button>
14</template>

Bonnes pratiques pour les plugins

  1. Provide/Inject - à préférer aux globalProperties
  2. Options avec valeurs par défaut - le plugin doit fonctionner sans configuration
  3. Typage - exporte des interfaces pour TypeScript
  4. Nettoyage - si le plugin modifie le DOM, assure-toi de bien nettoyer
  5. Documentation - décris les options et méthodes disponibles
Code de cette leçon : 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('fr')"
38          :class="['btn', { active: locale === 'fr' }]"
39        >FR</button>
40        <button
41          @click="setLocale('en')"
42          :class="['btn', { active: locale === 'en' }]"
43        >EN</button>
44      </div>
45      <p class="hint">Change de langue et observe comment les textes changent</p>
46    </section>
47
48    <!-- Conteneur des toasts -->
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>

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Comment implémenter des toasts qui disparaissent automatiquement après 3 secondes ?

  2. 2. Que fait la fonction t('nav.home') dans un plugin i18n ?

Tâches pratiques dans le jeu

  • Tri vertical

    Ordonne les étapes du fonctionnement de la méthode toast.show() dans le bon ordre :

  • Éditeur de code

    Implémente un ToastPlugin avec un tableau réactif de toasts et les méthodes show/success/error.

  • Clique dans l'ordre

    Ordonne la syntaxe correcte de app.provide dans un plugin :

  • Tri horizontal

    Ordonne l'appel inject correct pour récupérer le toast :