Construire un composant de notification (Message) dans Vue 3.0

Avec Vue 2.x, on utilisait souvent Vue.extend pour créer des composants modaux ou de notification. Dans Vue 3.0, cette API a été supprimée. Voici comment réaliser une alternative avec createApp et la fonction h.

À l'usage : Message.info('Info'); , Message.error('Erreur');

1. Ajouter un point de montage dans index.html

<html lang="fr">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title>Mon App</title>
  </head>
  <body>
    <div id="app"></div>
    <div id="notifications"></div>
  </body>
</html>

2. Créer le service Message (message.ts)

Plutôt qu’une classe, nous utilisons une factory qui retourne un objet avec les méthodes souhaitées.

import { createApp, h, reactive } from 'vue';
import NotificationComponent from './NotificationComponent.vue';

interface Notification {
  type: 'info' | 'warning' | 'success' | 'error';
  text: string;
}

const notifications = reactive<Notification[]>([]);
const DUREE_AFFICHAGE = 3000; // ms

function ajouterEtAfficher(type: Notification['type'], texte: string): void {
  // Ajouter une notification
  notifications.push({ type, text: texte });

  // Supprimer après délai
  setTimeout(() => {
    if (notifications.length > 0) {
      notifications.shift();
    }
  }, DUREE_AFFICHAGE);

  // Monter le composant s’il n’est pas déjà monté
  const cible = document.getElementById('notifications');
  if (cible && !cible.hasChildNodes()) {
    const app = createApp({
      render() {
        return h(NotificationComponent, { liste: notifications });
      }
    });
    app.mount('#notifications');
  }
}

export const Message = {
  info(texte: string): void {
    ajouterEtAfficher('info', texte);
  },
  warning(texte: string): void {
    ajouterEtAfficher('warning', texte);
  },
  success(texte: string): void {
    ajouterEtAfficher('success', texte);
  },
  error(texte: string): void {
    ajouterEtAfficher('error', texte);
  }
};

export default Message;

3. Composant d’affichgae (NotificationComponent.vue)

<template>
  <div class="notif-container">
    <div
      v-for="(notif, index) in liste"
      :key="index"
      :class="['notif-item', classeCss(notif.type)]"
    >
      {{ notif.text }}
    </div>
  </div>
</template>

<script setup lang="ts">
import { PropType } from 'vue';

interface Notification {
  type: 'info' | 'warning' | 'success' | 'error';
  text: string;
}

defineProps({
  liste: {
    type: Array as PropType<Notification[]>,
    default: () => []
  }
});

function classeCss(type: string): string {
  const map: Record<string, string> = {
    info: 'notif-info',
    warning: 'notif-warning',
    success: 'notif-success',
    error: 'notif-error'
  };
  return map[type] || '';
}
</script>

<style lang="scss" scoped>
.notif-container {
  position: fixed;
  top: 16px;
  right: 16px;
  z-index: 9999;
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-width: 280px;
}
.notif-item {
  padding: 10px 20px;
  border-radius: 4px;
  background: #f0f9ff;
  color: #0369a1;
  border: 1px solid #bae6fd;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  font-size: 14px;
}
.notif-info { background: #e0f2fe; border-color: #7dd3fc; color: #0369a1; }
.notif-warning { background: #fef9c3; border-color: #fde047; color: #a16207; }
.notif-success { background: #dcfce7; border-color: #86efac; color: #15803d; }
.notif-error { background: #fee2e2; border-color: #fca5a5; color: #b91c1c; }
</style>

4. Utilisation dans un composant

<template>
  <button @click="envoyerInfo">Afficher info</button>
  <button @click="envoyerErreur">Afficher erreur</button>
</template>

<script setup lang="ts">
import Message from '@/services/message';

function envoyerInfo() {
  Message.info('Opération réussie !');
}
function envoyerErreur() {
  Message.error('Une erreur est survenue.');
}
</script>

Étiquettes: Vue 3 createApp h function composant de notification TypeScript

Publié le 18 juillet à 00h27