Guide Technique Approfondi sur l'Écosystème Vue.js

Syntaxe Fondamentale et Réactivité

Gestion du Cycle de Vie et Enregistrement des Composants

L'enregistrement des composants et la gestion du cycle de vie ont connu des évolutions majeures entre les deux versions majeures du framework.

Approche Options API (Vue 2)

L'enregistrement global ou local nécessite une déclaration explicite dans les options du composant.

// Enregistrement global
import PrimaryActionBtn from './components/PrimaryActionBtn.vue'
Vue.component('PrimaryActionBtn', PrimaryActionBtn)

// Enregistrement local avec chargement paresseux
export default {
  components: {
    DashboardWidget: () => import('@/components/DashboardWidget.vue')
  }
}

Approche Composition API (Vue 3)

Avec le script setup, l'importation suffit. Le compilateur gère automatiquement l'enregistrement local.

<script setup>
import DashboardWidget from '@/components/DashboardWidget.vue'
</script>

Propriétés Calculées (Computed)

Les propriétés calculées permettent de dériver des états de manière réactive et mémorisée.

Vue 2 : Options API

export default {
  data() {
    return {
      baseCost: 100,
      serviceFee: 15
    }
  },
  computed: {
    finalAmount() {
      return this.baseCost + this.serviceFee
    },
    // Getter et Setter
    formattedAmount: {
      get() { return `$${this.finalAmount.toFixed(2)}` },
      set(val) { this.baseCost = parseFloat(val.replace('$', '')) - this.serviceFee }
    }
  }
}

Vue 3 : Composition API

import { ref, computed } from 'vue'

const rawUsers = ref([
  { id: 1, name: 'Alice', isActive: true },
  { id: 2, name: 'Bob', isActive: false }
])

// Syntaxe simplifiée
const activeUsers = computed(() => rawUsers.value.filter(u => u.isActive))

// Syntaxe avec getter/setter
const userCount = computed({
  get: () => rawUsers.value.length,
  set: (val) => { console.warn('Modification du compteur interceptée:', val) }
})

Observateurs (Watch)

Les observateurs sont utilisés pour exécuter des effets secondaires en réponse à des changements de données.

Vue 2 : Surveillance de données simples et imbriquées

export default {
  data() {
    return {
      searchQuery: '',
      userSettings: { theme: 'dark', notifications: true }
    }
  },
  watch: {
    searchQuery(newVal, oldVal) {
      console.log(`Recherche mise à jour: ${oldVal} -> ${newVal}`)
    },
    userSettings: {
      deep: true,
      handler(newSettings) {
        this.saveToLocalStorage(newSettings)
      }
    }
  }
}

Vue 3 : Surveillance avec l'API de composition

import { ref, watch } from 'vue'

const config = ref({ apiUrl: 'https://api.test.com', timeout: 5000 })

// Surveillance profonde d'un objet réactif
watch(config, (newConfig, oldConfig) => {
  console.log('Configuration modifiée', newConfig)
}, { deep: true, immediate: true })

// Surveillance d'une propriété spécifique
watch(() => config.value.timeout, (newTimeout) => {
  console.log('Délai d'attente mis à jour:', newTimeout)
})

Manipulation du DOM et nextTick

La méthode nextTick permet de différer l'exécution d'une fonction jusqu'à ce que le DOM soit mis à jour.

Vue 3 avec Script Setup

<script setup>
import { ref, nextTick, onMounted } from 'vue'

const isVisible = ref(false)
const messageRef = ref(null)

onMounted(async () => {
  isVisible.value = true
  // Le DOM n'est pas encore mis à jour ici
  await nextTick()
  // Le DOM est maintenant synchronisé
  console.log('Hauteur de l'élément:', messageRef.value.offsetHeight)
})
</script>

Directives Personnalisées

Les directives permettent d'appliquer des comportements basés sur le DOM.

Vue 3 : Focus automatique

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

app.directive('auto-focus', {
  mounted(el) {
    el.focus()
  }
})

// Utilisation dans le template : <input v-auto-focus />

Récupération des Instances et DefineExpose

Dans Vue 3, l'accès aux propriétés internes d'un composant enfant nécessite une exposition explicite.

// Composant Enfant (ChildComponent.vue)
<script setup>
import { ref } from 'vue'
const internalState = ref('Secret Data')
const resetState = () => { internalState.value = '' }

defineExpose({ internalState, resetState })
</script>

// Composant Parent
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

const childRef = ref(null)
const triggerReset = () => {
  childRef.value.resetState()
}
</script>
<template>
  <ChildComponent ref="childRef" />
</template>

Optimisation : Debounce avec customRef

Implémentation d'un reference personnalisée pour le debounce.

import { customRef } from 'vue'

export function useDebouncedRef(initialValue, delay = 300) {
  let timeout
  return customRef((track, trigger) => ({
    get() {
      track()
      return initialValue
    },
    set(newValue) {
      clearTimeout(timeout)
      timeout = setTimeout(() => {
        initialValue = newValue
        trigger()
      }, delay)
    }
  }))
}

// Utilisation
const searchInput = useDebouncedRef('', 500)


Architecture et Intégration des Modules

Gestion du Routage (Vue Router)

Configuration Vue 3 avec Vite

import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  { path: '/', redirect: '/dashboard' },
  { path: '/dashboard', component: () => import('@/views/Dashboard.vue') },
  { path: '/settings', component: () => import('@/views/Settings.vue') }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

router.beforeEach((to, from, next) => {
  const isAuthenticated = localStorage.getItem('auth_token')
  if (to.meta.requiresAuth && !isAuthenticated) {
    next('/login')
  } else {
    next()
  }
})

export default router

Client HTTP (Axios)

Configuration d'une instance Axios avec intercepteurs et gestion des variables d'environnement.

import axios from 'axios'

const apiClient = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 15000
})

apiClient.interceptors.request.use(config => {
  const authToken = localStorage.getItem('auth_token')
  if (authToken) {
    config.headers.Authorization = `Bearer ${authToken}`
  }
  return config
})

apiClient.interceptors.response.use(
  response => response.data,
  error => {
    if (error.response?.status === 401) {
      window.location.href = '/login'
    }
    return Promise.reject(error)
  }
)

export const fetchUserProfile = (userId) => apiClient.get(`/users/${userId}`)

Gestion d'État : Pinia

Pinia remplace avantageusement Vuex dans l'écosystème Vue 3, offrant une API plus intuitive et un support TypeScript natif.

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useShoppingCart = defineStore('cart', () => {
  const items = ref([])
  
  const totalItems = computed(() => items.value.reduce((sum, item) => sum + item.quantity, 0))
  
  const addItem = (product) => {
    const existing = items.value.find(i => i.id === product.id)
    if (existing) {
      existing.quantity++
    } else {
      items.value.push({ ...product, quantity: 1 })
    }
  }

  return { items, totalItems, addItem }
}, {
  persist: true // Nécessite le plugin pinia-plugin-persistedstate
})

// Utilisation dans un composant
import { storeToRefs } from 'pinia'
const cart = useShoppingCart()
const { items, totalItems } = storeToRefs(cart) // Conserve la réactivité

Intégration d'Element Plus

Configuration de l'importation automatique des composants et des styles via Vite.

// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  plugins: [
    vue(),
    AutoImport({ resolvers: [ElementPlusResolver()] }),
    Components({ resolvers: [ElementPlusResolver()] })
  ]
})

Encapsulation de Composants : Notification Toast

Création d'une fonction globale pour afficher des notifications dynamiques.

import { createApp, ref } from 'vue'
import ToastComponent from './Toast.vue'

const notifications = ref([])

export const showToast = (options) => {
  const container = document.createDocumentFragment()
  const app = createApp(ToastComponent, options)
  const instance = app.mount(container)
  document.body.appendChild(container)
  
  notifications.value.push(instance)
  
  setTimeout(() => {
    instance.hide()
    setTimeout(() => {
      app.unmount()
      notifications.value = notifications.value.filter(n => n !== instance)
    }, 300)
  }, options.duration || 3000)
}


Configuration et Démarrage du Projet

Initialisation avec Vite

La création d'un projet Vue 3 moderne utilise l'outil officiel basé sur Vite.

# Création du projet
npm init vue@latest

# Installation des dépendances
cd mon-projet
npm install

Configuration des Alias et du Proxy (vite.config.js)

import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  server: {
    proxy: {
      '/api': {
        target: 'http://backend.local:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
})


Concepts Internes et Résolution de Problèmes

Principes de Réactivité

  • Vue 2 : Utilise Object.defineProperty. Limite : ne peut pas détecter l'ajout/suppression de propriétés ou les modifications par index dans les tableaux. Nécessite Vue.set.
  • Vue 3 : Utilise Proxy. Permet une interception profonde, supporte les structures Map et Set, et détecte toutes les mutasions. Inconvénient : non supporté par IE11.

Computed vs Watch

  • Computed : Synchrone, possède un système de cache (ne se recalcule que si ses dépendances changent). Idéal pour les dérivations de données.
  • Watch : Supporte l'asynchrone, pas de cache. Idéal pour les effets secondaires (requêtes API, manipulation du DOM, stockage local).

Directives Conditionnelles et Boucles

  • v-if vs v-show : v-if détruit et recrée le nœud DOM (coûteux, bon pour les changements rares). v-show utilise display: none (toujours rendu, bon pour les basculements fréquents).
  • Priorité v-for / v-if : Dans Vue 2, v-for est prioritaire. Dans Vue 3, il est interdit de les placer sur le même nœud pour éviter les ambiguïtés de compiltaion.
  • Attribut key : Essentiel pour l'algorithme de Diff. Il permet à Vue d'identifier de manière unique les nœuds, optimisant ainsi le réemploi du DOM lors des réorganisations de listes.

Ordre d'Exécution du Cycle de Vie

Lors du rendu initial d'une arborescence de composants :

  1. Parent : beforeCreate -> created -> beforeMount
  2. Enfant : beforeCreate -> created -> beforeMount -> mounted
  3. Parent : mounted

Composants Intégrés de Vue 3

  • Suspense : Permet d'afficher un contenu de secours (fallback) pendant le chargement asynchrone de composants ou de données.
  • Teleport : Permet de projeter le DOM d'un composant à un autre endroit de l'arbre DOM (ex: modales attachées au body).
  • Fragment : Support natif des composants multi-racines sans nécessiter de balise enveloppante.

Utilitaires de l'API de Composition

  • toRef / toRefs : toRef crée une référence pour une propriété d'un objet réactif. toRefs convertit toutes les propriétés d'un objet réactif en références individuelles, permettant la déstructuration sans perte de réactivité.
  • toRaw : Retourne l'objet brut original, sans le proxy réactif. Utile pour sérialiser ou éviter de déclencher des effets secondaires.
  • markRaw : Marque un objet pour qu'il ne soit jamais converti en proxy. Utile pour les instances de bibliothèques tierces ou les données statiques volumineuses.
  • customRef : Permet de créer des références avec un contrôle explicite sur le suivi des dépendances et le déclenchement des mises à jour (ex: implémentation de debounce/throttle).

Étiquettes: vuejs Vue3 Composition-API Pinia Vue-Router

Publié le 26 juillet à 22h18