Project Oxygen & Ideo-LabIDEO LAB Dashboard 2026

💚 Vue.js – Le Guide Ultime

Deep Dive : Framework Progressif, Réactivité (ref/reactive), SFC (.vue), Pinia (State) & Nuxt (SSR).

1.1 Facile

1. C'est quoi Vue.js ?

Le Framework "Progressif". vs React vs Angular. v-model (Two-way binding).

Vue 3 Progressif
1.2 Facile

2. 🚀 SFC (Fichier .vue)

Single File Component. <template>, <script setup>, <style scoped>.

SFC script setup
1.3 Facile

3. Syntaxe Template

Interpolation ({{ }}), v-bind (:), v-on (@), v-html, v-model.

v-bind v-model
1.4 Avancé

4. RĂ©activitĂ© (Le CƓur)

Options API (data()) vs Composition API (ref, reactive).

ref reactive Composition
1.5 Moyen

5. Directives (v-if, v-for)

Rendu conditionnel (v-if vs v-show). Rendu de liste (v-for & :key).

v-if v-for :key
1.6 Facile

6. Gestion d'ÉvĂ©nements

@click, @submit. Modificateurs (.prevent, .stop, .enter).

@click .prevent
2.1 Moyen

7. Computed vs Watch

computed (caching) vs watch (side-effects, API calls).

computed watch
2.2 Moyen

8. Composants (Props)

Parent -> Enfant. defineProps(). Validation (type, required).

Components Props
2.3 Moyen

9. Composants (Emits)

Enfant -> Parent. defineEmits(), $emit('nom-event').

Emits $emit
2.4 Avancé

10. Lifecycle Hooks

mounted, created (Options) vs onMounted (Composition).

Lifecycle onMounted
3.1 Avancé

11. Addon: Vue Router

Le routeur officiel (SPA). createRouter, <router-link>, <router-view>.

vue-router SPA
3.2 Avancé

12. Addon: Pinia (State)

Le gestionnaire d'état (remplace Vuex). defineStore (state, getters, actions).

Pinia State
1.1 C'est quoi Vue.js ? (Le Framework Progressif)

Vue.js est un framework JavaScript open-source (créé par Evan You) pour construire des interfaces utilisateur (Front-End). Il est connu pour sa simplicité, sa performance et sa flexibilité.

Sa philosophie est celle du "Framework Progressif" :

  • Vous pouvez l'utiliser comme simple "librairie" (comme jQuery) pour "saupoudrer" de l'interactivitĂ© sur une page existante.
  • Ou vous pouvez l'utiliser comme un "framework" complet (SPA - Single Page Application) avec routage, gestion d'Ă©tat, et outils de build (comme React ou Angular).
Vue vs React vs Angular
FrameworkPhilosophieCourbe d'apprentissage
Angular (Google)Framework "Batteries Incluses". Rigide, opinioné, basé sur TypeScript.Difficile (beaucoup de concepts).
React (Meta)Librairie (View). Flexible, "Juste du JavaScript" (JSX). Vous choisissez le reste.Moyenne (JSX + écosystÚme).
Vue (Open Source)Framework Progressif. Le "meilleur des deux mondes". Simple (templates HTML) mais puissant (SFC, Composition API).Facile (la plus facile des trois).
La "Magie" : Réactivité & v-model (Two-Way Binding)

Vue est "réactif". Quand la donnée (JavaScript) change, le HTML se met à jour. v-model est le "sucre syntaxique" pour lier un input à une donnée.

                <!-- HTML -->
                <div id="app">
                <input type="text" v-model="message">
                <h1>Vous avez tapé : {{ message }}</h1>
                </div>

                <!-- JavaScript (Vue 3 - CDN) -->
                <script src="https://unpkg.com/vue@3"></script>
                <script>
                Vue.createApp({
                // 1. L'état (les données)
                data() {
                return {
                message: 'Bonjour Vue!'
                }
                }
                }).mount('#app')
                </script>
            
1.2 🚀 SFC (Single File Component) - Le Fichier .vue

La façon moderne (et professionnelle) de travailler avec Vue n'est pas le CDN, mais via un outil de build (comme Vite). Cela permet d'utiliser les Single File Components (Fichiers .vue).

Un fichier .vue encapsule la structure (HTML), la logique (JS) et le style (CSS) d'un composant dans un seul fichier.

Anatomie d'un Fichier MonComposant.vue
                <!-- ******************** -->
                <!-- 1. LE TEMPLATE (HTML) -->
                <!-- ******************** -->
                <template>
                <div class="card">
                <h1>{{ message }}</h1>
                <button @click="inverserMessage">Inverser</button>
                </div>
                </template>

                <!-- ****************** -->
                <!-- 2. LE SCRIPT (JS/TS) -->
                <!-- ****************** -->
                <script setup>
                // (C'est la "Composition API", voir 1.4)
                import { ref } from 'vue'

                // 2a. État rĂ©actif
                const message = ref('Bonjour Vue!')

                // 2b. Méthode
                function inverserMessage() {
                message.value = message.value.split('').reverse().join('')
                }
                </script>

                <!-- ****************** -->
                <!-- 3. LE STYLE (CSS/SCSS) -->
                <!-- ****************** -->
                <style scoped>
                /*
                'scoped' (le "plugin" magique)
                Ce style ne s'appliquera QU'À CE COMPOSANT.
                Il ne "fuira" pas (leak) aux autres composants.
                */
                .card {
                background: #f4f4f4;
                padding: 1rem;
                border-radius: 8px;
                }
                </style>
            
1.3 Syntaxe Template (Directives)

Le template Vue est du HTML "augmenté". Il utilise des "directives" (attributs spéciaux v-) pour lier le DOM aux données.

SyntaxeRaccourciDescription
Interpolation Texte : Affiche la donnée (message) en texte brut (échappé).
v-html="..."HTML Brut : Affiche la donnée en HTML (Dangereux ! Risque XSS).
v-bind:href="...":href="..."Binding Unidirectionnel : Lie une donnée à un attribut.
v-on:click="..."@click="..."Binding d'ÉvĂ©nement : Appelle une fonction au clic.
v-model="..."Binding Bidirectionnel : (Two-way) Lie un input à une donnée.
Exemple (v-bind vs v-model)
                <script setup>
                import { ref } from 'vue'
                const url = ref('https://vuejs.org')
                const message = ref('Editable')
                </script>

                <template>
                <!-- 1. v-bind (:) -->
                <!-- La donnée JS (url) pilote l'attribut HTML (href) -->
                <a :href="url">Lien vers Vue</a>

                <!-- 2. v-model -->
                <!-- 
                Binding bidirectionnel :
                - Si 'message' (JS) change, l'input (HTML) change.
                - Si l'input (HTML) change, 'message' (JS) change.
                -->
                <input type="text" v-model="message">
                </template>
            
1.4 Réactivité (Options API vs Composition API)

La "réactivité" est la magie de Vue. Quand une donnée JavaScript change, Vue le détecte et met à jour le DOM (HTML) automatiquement. Il y a deux façons d'écrire des composants :

Composition API (<script setup>)

La méthode moderne (recommandée). Plus flexible et plus proche du JS standard. On utilise ref et reactive pour *créer* la réactivité.

  • ref() : Pour les types primitifs (string, number, boolean). On accĂšde Ă  la valeur via .value (dans le script).
  • reactive() : Pour les objets ({...}) ou tableaux ([...]). On accĂšde directement.
                    <script setup>
                    import { ref, reactive, computed } from 'vue'

                    // 1. État rĂ©actif (primitif)
                    const compteur = ref(0) // (valeur: compteur.value)

                    // 2. État rĂ©actif (objet)
                    const user = reactive({
                    nom: 'Alice',
                    age: 30
                    })

                    // 3. Computed (basé sur l'état)
                    const estMajeur = computed(() => user.age >= 18)

                    // 4. Méthode
                    function incrementer() {
                    compteur.value++ // On doit utiliser .value !
                    user.age++
                    }
                    </script>

                    <template>
                    <!-- Dans le template, .value est automatique -->
                    <p>Compteur: {{ compteur }}</p>
                    <p>Utilisateur: {{ user.nom }} ({{ user.age }})</p>
                    <button @click="incrementer">+1</button>
                    </template>
                
Options API (Classique)

L'ancienne méthode (Vue 2), toujours 100% supportée. Plus structurée, mais moins flexible. Tout est "magiquement" réactif si c'est dans data().

                    <script>
                    export default {
                    // 1. L'état réactif
                    data() {
                    return {
                    compteur: 0,
                    user: {
                    nom: 'Alice',
                    age: 30
                    }
                    }
                    },

                    // 2. Computed
                    computed: {
                    estMajeur() {
                    // 'this' pointe vers l'instance
                    return this.user.age >= 18
                    }
                    },

                    // 3. Méthodes
                    methods: {
                    incrementer() {
                    // 'this' est magique
                    this.compteur++
                    this.user.age++
                    }
                    }
                    }
                    </script>

                    <template>
                    <!-- La syntaxe du template ne change PAS -->
                    <p>Compteur: {{ compteur }}</p>
                    <p>Utilisateur: {{ user.nom }} ({{ user.age }})</p>
                    <button @click="incrementer">+1</button>
                    </template>
                
1.5 Directives (v-if, v-for)
Rendu Conditionnel (v-if vs v-show)

Permet d'afficher ou de cacher des éléments.

DirectiveDescriptionPerformance
v-if / v-else-if / v-elseVrai rendu conditionnel. Les éléments sont détruits et recréés (retirés du DOM).Coûteux à basculer (toggle), mais léger au démarrage.
v-showBascule la propriété display: none en CSS. L'élément est *toujours* dans le DOM.Léger à basculer, mais coûteux au démarrage (l'élément est rendu).
Rendu de Liste (v-for)

La directive v-for permet de boucler sur un Array ou un Object.

L'attribut :key est obligatoire et crucial. Il permet Ă  Vue d'optimiser le rendu et d'Ă©viter les bugs de rĂ©activitĂ© (il doit ĂȘtre un string ou number unique).

                <script setup>
                import { ref } from 'vue'
                const visible = ref(true)
                const fruits = ref([
                { id: 1, nom: 'Pomme' },
                { id: 2, nom: 'Banane' },
                { id: 3, nom: 'Cerise' }
                ])
                </script>

                <template>
                <!-- v-if -->
                <div v-if="visible">
                Je suis visible.
                </div>
                <div v-else>
                Je suis caché.
                </div>

                <!-- v-for (sur Array) -->
                <ul>
                <li v-for="fruit in fruits" :key="fruit.id">
                {{ fruit.nom }}
                </li>
                </ul>

                <!-- v-for (avec index) -->
                <div v-for="(fruit, index) in fruits" :key="fruit.id">
                {{ index }} - {{ fruit.nom }}
                </div>
                </template>
            
1.6 Gestion d'ÉvĂ©nements (@click, .prevent)

On utilise la directive v-on: (raccourci @) pour écouter les événements du DOM (clic, soumission, clavier).

Modificateurs (La magie de Vue)

Vue fournit des "modificateurs" (.) pour gérer des cas courants sans écrire de logique event.preventDefault().

ModificateurDescription
.preventAppelle event.preventDefault(). (Ex: @submit.prevent sur un form).
.stopAppelle event.stopPropagation(). (EmpĂȘche l'Ă©vĂ©nement de "remonter" (bubbling)).
.onceL'événement ne se déclenchera qu'une seule fois.
.selfNe se déclenche que si l'événement vient de cet élément *exact* (pas d'un enfant).
Modificateurs de Touche (Key Modifiers)

On peut écouter des touches spécifiques.

  • @keydown.enter
  • @keydown.tab
  • @keydown.delete
  • @keydown.esc
  • @keydown.ctrl.enter (Combinaison)
Exemple Complet
                <script setup>
                function gererClic() {
                alert('Clic géré !');
                }
                function gererSubmit() {
                alert('Formulaire soumis (sans recharger la page)');
                }
                function gererEntree() {
                alert('Touche Entrée pressée !');
                }
                </script>

                <template>
                <!-- 1. Clic simple -->
                <button @click="gererClic">Cliquez ici</button>

                <!-- 2. Clic avec modificateur .stop -->
                <div @click="alert('Parent cliqué')">
                <button @click.stop="alert('Enfant cliqué')">Clic (stop)</button>
                </div>

                <!-- 3. Submit avec .prevent -->
                <form @submit.prevent="gererSubmit">
                <button type="submit">Envoyer</button>
                </form>

                <!-- 4. Touche clavier -->
                <input @keydown.enter="gererEntree">
                </template>
            
2.1 Computed (Propriétés Calculées) vs Watch (Observateurs)

C'est une confusion frĂ©quente. Les deux rĂ©agissent aux changements de donnĂ©es, mais n'ont pas le mĂȘme but.

computed (Propriétés Calculées)

Une computed est une propriété (pas une méthode) qui calcule sa valeur en fonction d'autres états réactifs.
La clé : Elle est **mise en cache** (cached). Elle ne se recalcule *que* si l'une de ses dépendances (ex: message) change.

Usage : Dériver un état (ex: un nom complet depuis nom et prenom), filtrer une liste.

watch (Observateurs)

Un watch "observe" une donnée réactive (ex: ref ou reactive) et exécute une fonction (un "side effect") chaque fois qu'elle change.
La clé : Il n'est **pas** mis en cache et ne renvoie rien.

Usage : Créer des "side effects". (Ex: "Quand userId change, lance un appel API pour chercher ce user").

Exemple (Composition API)
                <script setup>
                import { ref, computed, watch } from 'vue'

                const prenom = ref('Jean')
                const nom = ref('Dupont')

                // 1. COMPUTED (Mis en cache)
                // Se recalcule *seulement* si 'prenom' ou 'nom' change.
                const nomComplet = computed(() => {
                console.log('Calcul de nomComplet...')
                return `${prenom.value} ${nom.value.toUpperCase()}`
                })

                // 2. WATCH (Side Effect)
                // S'exécute *chaque* fois que 'nom' change.
                watch(nom, (nouveauNom, ancienNom) => {
                console.log(`Le nom a changé de '${ancienNom}' à '${nouveauNom}'`)
                // (Ex: Lancer un appel API pour vérifier si le nom est dispo)
                })
                </script>

                <template>
                <input v-model="prenom">
                <input v-model="nom">

                <!-- 'nomComplet' est utilisé comme une variable -->
                <h3>Nom complet: {{ nomComplet }}</h3>
                </template>
            
2.2 Composants (Props : Parent -> Enfant)

Les composants sont la base de Vue. Les Props (Propriétés) sont le mécanisme pour passer des données du Parent vers l'Enfant.

Le flux est unidirectionnel : le parent peut passer une prop Ă  l'enfant, mais l'enfant ne doit *jamais* modifier la prop (c'est une "mutation" interdite).

Parent.vue (Celui qui envoie)
                        <script setup>
                        import { ref } from 'vue'
                        import BlogPost from './BlogPost.vue'

                        const post1 = ref({
                        id: 1,
                        titre: 'Mon premier article'
                        })
                        const post2 = ref({
                        id: 2,
                        titre: 'Mon deuxiĂšme article'
                        })
                        </script>

                        <template>
                        <!-- 
                        1. On utilise le composant
                        2. On "bind" (lie) la donnée parent (post1) 
                        Ă  la "prop" enfant ('titre-article')
                        -->
                        <BlogPost :titre-article="post1.titre" :likes="15" />

                        <!-- On peut binder l'objet entier -->
                        <BlogPost v-bind="post2" /> <!-- (mauvaise pratique) -->
                        </template>
                    
BlogPost.vue (Celui qui reçoit)

Avec <script setup>, on utilise defineProps.

                        <script setup>
                        // 1. Déclaration simple (array de strings)
                        // const props = defineProps(['titreArticle', 'likes'])

                        // 2. Déclaration avec Validation (MEILLEURE)
                        const props = defineProps({
                        titreArticle: {
                        type: String,
                        required: true
                        },
                        likes: {
                        type: Number,
                        default: 0
                        }
                        })
                        </script>

                        <template>
                        <div class="post">
                        <!-- 3. Utilisation de la prop -->
                        <h3>{{ props.titreArticle }}</h3>
                        <p>Likes: {{ props.likes }}</p>
                        </div>
                        </template>
                    
2.3 Composants (Emits : Enfant -> Parent)

L'enfant ne peut pas modifier la prop du parent. Pour communiquer "vers le haut" (Enfant -> Parent), l'enfant doit émettre (emit) un événement.

Enfant.vue (ex: AlerteCustom.vue)

L'enfant déclare les événements qu'il peut émettre (defineEmits) et les appelle ($emit).

                        <script setup>
                        // 1. Déclare les événements que ce composant *peut* émettre
                        const emit = defineEmits(['fermer', 'accepter'])

                        function surClicFermer() {
                        // 2. Émet l'Ă©vĂ©nement "fermer" (que le parent Ă©coute)
                        emit('fermer')
                        }

                        function surClicAccepter() {
                        // 3. Émet un Ă©vĂ©nement AVEC des donnĂ©es (payload)
                        emit('accepter', { userId: 123, status: 'ok' })
                        }
                        </script>

                        <template>
                        <div class="alerte">
                        <p>Voulez-vous accepter ?</p>
                        <button @click="surClicAccepter">Accepter</button>
                        <button @click="surClicFermer">Fermer (X)</button>
                        </div>
                        </template>
                    
Parent.vue

Le parent "écoute" (@) les événements émis par l'enfant.

                        <script setup>
                        import AlerteCustom from './AlerteCustom.vue'

                        function gererFermeture() {
                        console.log("Le parent a reçu l'évent 'fermer'!")
                        }

                        // L'objet 'payload' (de l'enfant) est passé en argument
                        function gererAcceptation(payload) {
                        console.log("Le parent a reçu 'accepter' !")
                        console.log(payload.userId) // 123
                        }
                        </script>

                        <template>
                        <!-- 
                        Le parent écoute '@fermer' et '@accepter' 
                        (les noms définis dans 'defineEmits' de l'enfant)
                        -->
                        <AlerteCustom
                        @fermer="gererFermeture"
                        @accepter="gererAcceptation"
                        />
                        </template>
                    
2.4 Lifecycle Hooks (Crochets de Cycle de Vie)

Chaque composant Vue passe par un cycle de vie (Création, Montage, Mise à jour, Démontage). Les "Hooks" sont des fonctions que l'on peut "accrocher" à ces moments clés.

Les 4 Hooks Principaux
Hook (Options API)Hook (Composition API)Quand ?Cas d'usage
created(<script setup>)Création : L'instance est créée (data, props...).Logique "non-DOM" (ex: fetch data).
mountedonMountedMontage : Le composant est inséré dans le DOM.Le plus utilisé. AccÚs au DOM (ex: document.getElementById), initialiser une lib (Chart.js).
updatedonUpdatedMise à jour : Une donnée réactive a changé ET le DOM a été re-rendu.Accéder au DOM *aprÚs* une mise à jour (rare).
unmountedonUnmountedDémontage : Le composant est retiré du DOM (ex: v-if="false").Nettoyage (Cleanup). (ex: clearInterval, removeEventListener).
Exemple (onMounted)
                <script setup>
                import { ref, onMounted, onUnmounted } from 'vue'

                const timer = ref(0)
                let intervalId = null

                // 1. Hook "onMounted"
                // (S'exécute 1x, quand le composant est dans le DOM)
                onMounted(() => {
                console.log("Composant monté !");

                // Ex: Démarrer un timer
                intervalId = setInterval(() => {
                timer.value++
                }, 1000)
                })

                // 2. Hook "onUnmounted" (Nettoyage)
                // (S'exécute 1x, quand le composant est détruit)
                onUnmounted(() => {
                console.log("Composant démonté ! Nettoyage...");
                // CRUCIAL: Nettoyer le timer pour éviter les fuites mémoire
                clearInterval(intervalId)
                })
                </script>
            
3.1 Addon: Vue Router (Gestion des SPAs)

Pour une Single Page Application (SPA), on ne change pas de page (pas de rechargement). On change *le composant* affiché. Vue Router est le "plugin" officiel pour gérer cela.

1. Définir les Routes (router.js)
                import { createRouter, createWebHistory } from 'vue-router'
                import Home from './views/Home.vue'
                import About from './views/About.vue'
                import UserProfile from './views/UserProfile.vue'

                // 1. Définir les "routes"
                const routes = [
                { path: '/', component: Home },
                { path: '/about', component: About },

                // Route dynamique (avec "params")
                { path: '/user/:id', component: UserProfile, props: true }
                ]

                // 2. Créer l'instance du routeur
                const router = createRouter({
                history: createWebHistory(), // (Utilise l'historique HTML5)
                routes,
                })

                export default router
            
2. L'utiliser (App.vue)

Dans votre composant principal (App.vue), vous utilisez 2 composants du routeur :

  • <router-link> : Le "<a>" de Vue. GĂ©nĂšre un lien sans recharger la page.
  • <router-view> : Le "placeholder". C'est lĂ  que le composant de la route (Home, About) sera injectĂ©.
                <!-- App.vue -->
                <template>
                <header>
                <nav>
                <router-link to="/">Accueil</router-link>
                <router-link to="/about">À Propos</router-link>
                <router-link to="/user/123">Utilisateur 123</router-link>
                </nav>
                </header>

                <main>
                <!-- La "fenĂȘtre" oĂč les pages s'affichent -->
                <router-view />
                </main>
                </template>
            
3.2 Addon: Pinia (Gestion d'État)

Le ProblÚme (Prop Drilling) : Que faire si un composant (Niveau 1) doit partager une donnée (ex: user) avec un composant (Niveau 5) ? On doit "percer" (drill) la prop à travers 4 niveaux. C'est un cauchemar.

La Solution : Un Gestionnaire d'État Global (State Management).
Pinia (prononcĂ© "pi-nia") est le gestionnaire d'Ă©tat officiel (et moderne) de Vue (il remplace l'ancien Vuex). C'est un "store" global oĂč l'on place l'Ă©tat partagĂ©.

1. Définir un "Store" (/stores/userStore.js)
                import { defineStore } from 'pinia'

                // 1. Définir le "store"
                export const useUserStore = defineStore('user', {

                // 2. 'state' (les données)
                state: () => ({
                isLoggedIn: false,
                username: 'Guest'
                }),

                // 3. 'getters' (les "computed" du store)
                getters: {
                welcomeMessage: (state) => `Bienvenue, ${state.username}`
                },

                // 4. 'actions' (les "méthodes" pour changer l'état)
                actions: {
                login(newUsername) {
                this.username = newUsername
                this.isLoggedIn = true
                },
                logout() {
                this.username = 'Guest'
                this.isLoggedIn = false
                }
                }
                })
            
2. L'utiliser (dans n'importe quel composant)
                <!-- Composant A (Navbar.vue) -->
                <script setup>
                import { useUserStore } from '@/stores/userStore'
                const userStore = useUserStore() // Accroche au store
                </script>
                <template>
                <div>{{ userStore.welcomeMessage }}</div>
                </template>

                <!-- Composant B (Login.vue) -->
                <script setup>
                import { useUserStore } from '@/stores/userStore'
                const userStore = useUserStore()
                </script>
                <template>
                <button @click="userStore.login('Alice')">Login</button>
                </template>
            

Quand le bouton (Composant B) est cliqué, l'état username change, et le Navbar (Composant A) se met à jour *automatiquement*.