Frameworks

Vue.js : Le Guide Complet du Framework JavaScript (2025)

Mark Toledo

Mark Toledo

16 janvier 2025

Vue.js : Le Guide Complet du Framework JavaScript (2025)

Vue.js est un framework JavaScript progressif pour construire des interfaces utilisateur. Créé par Evan You en 2014, Vue.js s'est imposé comme l'un des trois grands frameworks frontend aux côtés de React et Angular.

Ce guide complet vous accompagne dans l'apprentissage de Vue.js 3, la dernière version majeure avec la Composition API.

Pourquoi choisir Vue.js ?

Vue.js se distingue par plusieurs atouts :

  • Courbe d'apprentissage douce : Facile à prendre en main pour les débutants
  • Documentation exemplaire : La meilleure documentation de l'écosystème JavaScript
  • Progressif : Utilisez-le pour un widget ou une application complète
  • Performant : Virtual DOM optimisé et réactivité fine
  • Écosystème cohérent : Vue Router, Pinia, Nuxt officiels

Installation de Vue.js

Avec create-vue (recommandé)

npm create vue@latest mon-projet

# Répondre aux questions :
# ✔ Add TypeScript? Yes
# ✔ Add Vue Router? Yes
# ✔ Add Pinia? Yes
# ✔ Add Vitest? Yes
# ✔ Add ESLint? Yes

cd mon-projet
npm install
npm run dev

Via CDN (prototypage)

<!DOCTYPE html>
<html>
<head>
  <title>Vue.js CDN</title>
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
  <div id="app">{{ message }}</div>

  <script>
    const { createApp, ref } = Vue

    createApp({
      setup() {
        const message = ref('Bonjour Vue.js !')
        return { message }
      }
    }).mount('#app')
  </script>
</body>
</html>

Structure d'un projet Vue.js

mon-projet/
├── public/              # Fichiers statiques
├── src/
│   ├── assets/          # CSS, images
│   ├── components/      # Composants réutilisables
│   ├── composables/     # Fonctions réutilisables
│   ├── router/          # Configuration des routes
│   ├── stores/          # State management (Pinia)
│   ├── views/           # Pages/vues
│   ├── App.vue          # Composant racine
│   └── main.ts          # Point d'entrée
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.ts

Les Single File Components (SFC)

Les composants Vue.js utilisent le format .vue :

<!-- MonComposant.vue -->
<script setup>
import { ref, computed } from 'vue'

// State réactif
const count = ref(0)

// Computed property
const doubled = computed(() => count.value * 2)

// Méthode
function increment() {
  count.value++
}
</script>

<template>
  <div class="counter">
    <p>Compteur : {{ count }}</p>
    <p>Doublé : {{ doubled }}</p>
    <button @click="increment">+1</button>
  </div>
</template>

<style scoped>
.counter {
  padding: 1rem;
  border: 1px solid #ccc;
  border-radius: 8px;
}

button {
  padding: 0.5rem 1rem;
  cursor: pointer;
}
</style>

Réactivité avec Vue.js

ref() pour les valeurs primitives

<script setup>
import { ref } from 'vue'

const message = ref('Bonjour')
const count = ref(0)
const isVisible = ref(true)

// Modifier la valeur avec .value
function update() {
  message.value = 'Hello'
  count.value++
  isVisible.value = !isVisible.value
}
</script>

<template>
  <p>{{ message }}</p>
  <p>{{ count }}</p>
  <button @click="update">Modifier</button>
</template>

reactive() pour les objets

<script setup>
import { reactive } from 'vue'

const state = reactive({
  user: {
    name: 'Alice',
    age: 25
  },
  items: []
})

// Modifier directement (pas besoin de .value)
function addItem(item) {
  state.items.push(item)
}

function updateUser(name) {
  state.user.name = name
}
</script>

computed() pour les valeurs dérivées

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

const items = ref([
  { name: 'Pomme', price: 1.5 },
  { name: 'Banane', price: 0.8 },
  { name: 'Orange', price: 2.0 }
])

// Computed recalculé automatiquement
const total = computed(() => {
  return items.value.reduce((sum, item) => sum + item.price, 0)
})

const expensive = computed(() => {
  return items.value.filter(item => item.price > 1)
})
</script>

<template>
  <ul>
    <li v-for="item in items" :key="item.name">
      {{ item.name }} - {{ item.price }}€
    </li>
  </ul>
  <p>Total : {{ total }}€</p>
  <p>Articles chers : {{ expensive.length }}</p>
</template>

watch() pour réagir aux changements

<script setup>
import { ref, watch, watchEffect } from 'vue'

const searchQuery = ref('')
const results = ref([])

// watch explicite
watch(searchQuery, async (newQuery) => {
  if (newQuery.length > 2) {
    results.value = await fetchResults(newQuery)
  }
})

// watchEffect automatique
watchEffect(() => {
  console.log(`Recherche: ${searchQuery.value}`)
})
</script>

Directives Vue.js

v-bind (liaison d'attributs)

<template>
  <!-- Syntaxe longue -->
  <img v-bind:src="imageUrl" v-bind:alt="imageAlt" />

  <!-- Syntaxe courte (recommandée) -->
  <img :src="imageUrl" :alt="imageAlt" />

  <!-- Classes dynamiques -->
  <div :class="{ active: isActive, error: hasError }"></div>
  <div :class="[baseClass, isActive ? 'active' : '']"></div>

  <!-- Styles dynamiques -->
  <div :style="{ color: textColor, fontSize: fontSize + 'px' }"></div>
</template>

v-on (événements)

<template>
  <!-- Syntaxe longue -->
  <button v-on:click="handleClick">Cliquer</button>

  <!-- Syntaxe courte (recommandée) -->
  <button @click="handleClick">Cliquer</button>

  <!-- Avec paramètres -->
  <button @click="handleClick($event, 'param')">Cliquer</button>

  <!-- Modificateurs -->
  <form @submit.prevent="onSubmit">...</form>
  <input @keyup.enter="search" />
  <div @click.stop="onClick">Stop propagation</div>
</template>

v-model (two-way binding)

<script setup>
import { ref } from 'vue'

const name = ref('')
const age = ref(0)
const agreed = ref(false)
const selected = ref('option1')
</script>

<template>
  <!-- Input texte -->
  <input v-model="name" placeholder="Nom" />

  <!-- Input nombre -->
  <input v-model.number="age" type="number" />

  <!-- Checkbox -->
  <input v-model="agreed" type="checkbox" />

  <!-- Select -->
  <select v-model="selected">
    <option value="option1">Option 1</option>
    <option value="option2">Option 2</option>
  </select>

  <!-- Textarea avec trim -->
  <textarea v-model.trim="description"></textarea>
</template>

v-if / v-else / v-show

<template>
  <!-- Rendu conditionnel (ajoute/supprime du DOM) -->
  <div v-if="status === 'loading'">Chargement...</div>
  <div v-else-if="status === 'error'">Erreur !</div>
  <div v-else>Contenu chargé</div>

  <!-- v-show (toggle CSS display) -->
  <div v-show="isVisible">Toujours dans le DOM</div>
</template>

v-for (boucles)

<script setup>
const items = ref([
  { id: 1, name: 'Item 1' },
  { id: 2, name: 'Item 2' },
  { id: 3, name: 'Item 3' }
])
</script>

<template>
  <!-- Liste avec key obligatoire -->
  <ul>
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
    </li>
  </ul>

  <!-- Avec index -->
  <ul>
    <li v-for="(item, index) in items" :key="item.id">
      {{ index }}: {{ item.name }}
    </li>
  </ul>

  <!-- Objet -->
  <div v-for="(value, key) in object" :key="key">
    {{ key }}: {{ value }}
  </div>
</template>

Props et Events

Définir des props

<!-- ChildComponent.vue -->
<script setup>
// Définition simple
const props = defineProps(['title', 'count'])

// Avec validation (recommandé)
const props = defineProps({
  title: {
    type: String,
    required: true
  },
  count: {
    type: Number,
    default: 0
  },
  items: {
    type: Array,
    default: () => []
  }
})
</script>

<template>
  <h2>{{ title }}</h2>
  <p>Count: {{ count }}</p>
</template>

Émettre des événements

<!-- ChildComponent.vue -->
<script setup>
const emit = defineEmits(['update', 'delete'])

function handleUpdate() {
  emit('update', { id: 1, data: 'nouvelle valeur' })
}

function handleDelete() {
  emit('delete', 1)
}
</script>
<!-- ParentComponent.vue -->
<template>
  <ChildComponent
    title="Mon titre"
    :count="5"
    @update="onUpdate"
    @delete="onDelete"
  />
</template>

<script setup>
function onUpdate(payload) {
  console.log('Update:', payload)
}

function onDelete(id) {
  console.log('Delete:', id)
}
</script>

Composables (hooks personnalisés)

Créer un composable

// composables/useCounter.ts
import { ref, computed } from 'vue'

export function useCounter(initial = 0) {
  const count = ref(initial)

  const doubled = computed(() => count.value * 2)

  function increment() {
    count.value++
  }

  function decrement() {
    count.value--
  }

  function reset() {
    count.value = initial
  }

  return {
    count,
    doubled,
    increment,
    decrement,
    reset
  }
}

Utiliser un composable

<script setup>
import { useCounter } from '@/composables/useCounter'

const { count, doubled, increment, decrement, reset } = useCounter(10)
</script>

<template>
  <p>Count: {{ count }}</p>
  <p>Doubled: {{ doubled }}</p>
  <button @click="increment">+</button>
  <button @click="decrement">-</button>
  <button @click="reset">Reset</button>
</template>

Composable avec fetch

// composables/useFetch.ts
import { ref, watchEffect } from 'vue'

export function useFetch<T>(url: string) {
  const data = ref<T | null>(null)
  const error = ref<Error | null>(null)
  const loading = ref(true)

  async function fetchData() {
    loading.value = true
    error.value = null

    try {
      const response = await fetch(url)
      data.value = await response.json()
    } catch (e) {
      error.value = e as Error
    } finally {
      loading.value = false
    }
  }

  watchEffect(() => {
    fetchData()
  })

  return { data, error, loading, refetch: fetchData }
}

Routing avec Vue Router

Configuration

// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', name: 'home', component: Home },
    { path: '/about', name: 'about', component: () => import('@/views/About.vue') },
    { path: '/user/:id', name: 'user', component: () => import('@/views/User.vue') }
  ]
})

export default router

Navigation

<template>
  <nav>
    <RouterLink to="/">Accueil</RouterLink>
    <RouterLink :to="{ name: 'about' }">À propos</RouterLink>
    <RouterLink :to="{ name: 'user', params: { id: 123 } }">Profil</RouterLink>
  </nav>

  <RouterView />
</template>

<script setup>
import { useRouter, useRoute } from 'vue-router'

const router = useRouter()
const route = useRoute()

// Navigation programmatique
function goToUser(id) {
  router.push({ name: 'user', params: { id } })
}

// Accéder aux paramètres
console.log(route.params.id)
</script>

State Management avec Pinia

Créer un store

// stores/counter.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCounterStore = defineStore('counter', () => {
  // State
  const count = ref(0)

  // Getters
  const doubled = computed(() => count.value * 2)

  // Actions
  function increment() {
    count.value++
  }

  function decrement() {
    count.value--
  }

  return { count, doubled, increment, decrement }
})

Utiliser le store

<script setup>
import { useCounterStore } from '@/stores/counter'

const counter = useCounterStore()
</script>

<template>
  <p>Count: {{ counter.count }}</p>
  <p>Doubled: {{ counter.doubled }}</p>
  <button @click="counter.increment">+</button>
  <button @click="counter.decrement">-</button>
</template>

Conclusion

Vue.js 3 est un framework mature et puissant pour créer des applications web modernes. Avec la Composition API, vous avez un contrôle total sur la logique de vos composants tout en gardant un code lisible et maintenable.

Prochaines étapes :