Skip to content

Instantly share code, notes, and snippets.

@stephdl
Created February 21, 2026 18:14
Show Gist options
  • Select an option

  • Save stephdl/a8f28ec87d1ade7267a943c05639db77 to your computer and use it in GitHub Desktop.

Select an option

Save stephdl/a8f28ec87d1ade7267a943c05639db77 to your computer and use it in GitHub Desktop.
Vue.js 2 & 3

Vue.js 2 & 3 - Guide Complet et Documentation Approfondie

Documentation complète pour développeurs Vue.js - Du débutant à l'expert

Par un développeur passionné d'open source | Dernière mise à jour : Février 2026


📑 Table des matières

  1. Introduction & Mise en place
  2. Anatomie d'un composant
  3. Réactivité
  4. Propriétés calculées (Computed)
  5. Watchers
  6. Méthodes (Methods)
  7. Lifecycle Hooks
  8. Props & Events
  9. Composables
  10. Gestion d'état (Store)
  11. Routing (Vue Router)
  12. Organisation du projet
  13. Helpers & Utilities
  14. Directives personnalisées
  15. Plugins & Librairies
  16. Performance & Optimisation
  17. Testing
  18. Best Practices & Patterns
  19. Slots & Content Distribution
  20. Teleport & Suspense
  21. Mixins & Composition
  22. Render Functions & JSX
  23. Transitions & Animations
  24. Formulaires avancés
  25. Internationalisation (i18n)
  26. SSR & SSG
  27. API & Gestion des requêtes
  28. State Management avancé
  29. TypeScript avancé
  30. Development Tools
  31. Sécurité
  32. Accessibilité (a11y)
  33. Mobile & Progressive Web Apps
  34. Debugging & Troubleshooting
  35. Ecosystem & Intégrations
  36. Patterns de design
  37. Real-world Examples
  38. Annexes

1. Introduction & Mise en place

🔄 Comparaison Vue 2 vs Vue 3

Fonctionnalité Vue 2 Vue 3
Performance Baseline ~55% plus rapide
Bundle size ~22kb ~16kb
Réactivité Object.defineProperty Proxy
API Options API Options + Composition
TypeScript Support basique Support natif
Fragments ❌ Un seul root ✅ Multiple roots
Teleport
Suspense
Multiple v-model

Installation

Vue 3 avec Vite (recommandé) :

npm create vite@latest mon-projet -- --template vue-ts
cd mon-projet
npm install
npm run dev

Vue 2 avec Vue CLI :

npm install -g @vue/cli
vue create mon-projet

Configuration de base

vite.config.ts :

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

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  server: {
    port: 3000,
    open: true
  }
})

2. Anatomie d'un composant

Options API (Vue 2 & 3)

<template>
  <div class="counter">
    <h1>{{ title }}</h1>
    <p>Count: {{ count }}</p>
    <p>Double: {{ doubleCount }}</p>
    <button @click="increment">+1</button>
  </div>
</template>

<script>
export default {
  name: 'Counter',
  
  props: {
    initialCount: {
      type: Number,
      default: 0
    }
  },
  
  data() {
    return {
      title: 'My Counter',
      count: this.initialCount
    }
  },
  
  computed: {
    doubleCount() {
      return this.count * 2
    }
  },
  
  watch: {
    count(newVal, oldVal) {
      console.log(`Count: ${oldVal}${newVal}`)
    }
  },
  
  methods: {
    increment() {
      this.count++
      this.$emit('update', this.count)
    }
  },
  
  mounted() {
    console.log('Component mounted')
  }
}
</script>

<style scoped>
.counter {
  padding: 20px;
  border: 1px solid #42b983;
}
</style>

Composition API (Vue 3)

<template>
  <div class="counter">
    <h1>{{ title }}</h1>
    <p>Count: {{ count }}</p>
    <p>Double: {{ doubleCount }}</p>
    <button @click="increment">+1</button>
  </div>
</template>

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

export default {
  props: {
    initialCount: {
      type: Number,
      default: 0
    }
  },
  
  setup(props, { emit }) {
    const title = ref('My Counter')
    const count = ref(props.initialCount)
    
    const doubleCount = computed(() => count.value * 2)
    
    watch(count, (newVal, oldVal) => {
      console.log(`Count: ${oldVal}${newVal}`)
    })
    
    const increment = () => {
      count.value++
      emit('update', count.value)
    }
    
    onMounted(() => {
      console.log('Component mounted')
    })
    
    return {
      title,
      count,
      doubleCount,
      increment
    }
  }
}
</script>

Script Setup (Vue 3 - Recommandé)

<template>
  <div class="counter">
    <h1>{{ title }}</h1>
    <p>Count: {{ count }}</p>
    <p>Double: {{ doubleCount }}</p>
    <button @click="increment">+1</button>
  </div>
</template>

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

const props = defineProps({
  initialCount: {
    type: Number,
    default: 0
  }
})

const emit = defineEmits(['update'])

const title = ref('My Counter')
const count = ref(props.initialCount)

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

watch(count, (newVal, oldVal) => {
  console.log(`Count: ${oldVal}${newVal}`)
})

const increment = () => {
  count.value++
  emit('update', count.value)
}

onMounted(() => {
  console.log('Component mounted')
})
</script>

3. Réactivité

🔄 Vue 2 vs Vue 3

Opération Vue 2 Vue 3
Ajouter propriété this.$set(obj, 'key', val) obj.key = val
Supprimer propriété this.$delete(obj, 'key') delete obj.key
Array par index this.$set(arr, 0, val) arr[0] = val
Map/Set ❌ Non supporté ✅ Supporté

Vue 2 - Limitations

// Vue 2
export default {
  data() {
    return {
      user: { name: 'John' },
      items: [1, 2, 3]
    }
  },
  methods: {
    updateUser() {
      // ❌ Non réactif
      this.user.email = 'john@example.com'
      this.items[0] = 99
      
      // ✅ Réactif
      this.$set(this.user, 'email', 'john@example.com')
      this.$set(this.items, 0, 99)
      
      // ✅ Ou recréer
      this.user = { ...this.user, email: 'john@example.com' }
      this.items = [99, ...this.items.slice(1)]
    }
  }
}

Vue 3 - Tout réactif

// Vue 3
import { reactive, ref } from 'vue'

const user = reactive({ name: 'John' })
const items = ref([1, 2, 3])

// ✅ Tout fonctionne !
user.email = 'john@example.com'
items.value[0] = 99
delete user.name

ref() vs reactive()

import { ref, reactive, toRefs } from 'vue'

// ref() - Pour primitives et objets
const count = ref(0)
const user = ref({ name: 'John' })

count.value++ // Besoin de .value
user.value.name = 'Jane'
user.value = { name: 'New' } // ✅ Peut remplacer

// reactive() - Pour objets seulement
const state = reactive({
  count: 0,
  user: { name: 'John' }
})

state.count++ // Pas de .value
state.user.name = 'Jane'
// state = { count: 1 } // ❌ Ne pas réassigner !

// toRefs() - Pour déstructurer sans perdre réactivité
const { count: refCount, user: refUser } = toRefs(state)
refCount.value++ // Maintenant c'est une ref

4. Propriétés calculées (Computed)

Vue 2 - Options API

export default {
  data() {
    return {
      firstName: 'John',
      lastName: 'Doe',
      cart: [
        { name: 'Item 1', price: 10, qty: 2 },
        { name: 'Item 2', price: 20, qty: 1 }
      ]
    }
  },
  
  computed: {
    // Simple getter
    fullName() {
      return `${this.firstName} ${this.lastName}`
    },
    
    // Avec logique
    totalPrice() {
      return this.cart.reduce((sum, item) => {
        return sum + (item.price * item.qty)
      }, 0)
    },
    
    // Getter + Setter
    fullNameEditable: {
      get() {
        return `${this.firstName} ${this.lastName}`
      },
      set(value) {
        const [first, last] = value.split(' ')
        this.firstName = first
        this.lastName = last
      }
    }
  }
}

Vue 3 - Composition API

import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

// Simple
const fullName = computed(() => {
  return `${firstName.value} ${lastName.value}`
})

// Getter + Setter
const fullNameEditable = computed({
  get: () => `${firstName.value} ${lastName.value}`,
  set: (value) => {
    const [first, last] = value.split(' ')
    firstName.value = first
    lastName.value = last
  }
})

// Usage
console.log(fullName.value)
fullNameEditable.value = 'Jane Smith'

5. Watchers

Vue 2 - Options API

export default {
  data() {
    return {
      question: '',
      answer: '',
      user: { name: 'John', age: 30 }
    }
  },
  
  watch: {
    // Simple watcher
    question(newVal, oldVal) {
      console.log(`Question: ${oldVal}${newVal}`)
      this.getAnswer()
    },
    
    // Avec options
    question: {
      handler(newVal) {
        this.getAnswer()
      },
      immediate: true,  // Exécuter immédiatement
      deep: false
    },
    
    // Deep watcher
    user: {
      handler(newUser) {
        console.log('User changed:', newUser)
      },
      deep: true  // Observer changements profonds
    },
    
    // Propriété imbriquée
    'user.name'(newName) {
      console.log('Name changed:', newName)
    }
  },
  
  methods: {
    getAnswer() {
      // Recherche de réponse
    }
  }
}

Vue 3 - Composition API

import { ref, watch, watchEffect } from 'vue'

const question = ref('')
const user = ref({ name: 'John', age: 30 })

// watch() - Dépendances explicites
watch(question, (newVal, oldVal) => {
  console.log(`Question: ${oldVal}${newVal}`)
})

// Watch avec options
watch(question, (newVal) => {
  console.log('Question:', newVal)
}, {
  immediate: true,
  deep: false
})

// Watch multiple sources
watch([question, user], ([newQ, newU], [oldQ, oldU]) => {
  console.log('Question:', newQ)
  console.log('User:', newU)
})

// Deep watch
watch(user, (newUser) => {
  console.log('User changed:', newUser)
}, { deep: true })

// watchEffect() - Track automatique
watchEffect(() => {
  // Réagit automatiquement à tous les refs utilisés
  console.log(`Question: ${question.value}`)
  console.log(`User: ${user.value.name}`)
})

// Avec cleanup
watch(question, async (newVal, oldVal, onCleanup) => {
  const controller = new AbortController()
  
  onCleanup(() => {
    controller.abort()
  })
  
  try {
    const response = await fetch('/api/search', {
      signal: controller.signal
    })
  } catch (error) {
    if (error.name !== 'AbortError') {
      console.error(error)
    }
  }
})

6. Méthodes (Methods)

Vue 2 - Options API

export default {
  data() {
    return {
      count: 0,
      users: []
    }
  },
  
  methods: {
    // Méthode simple
    increment() {
      this.count++
    },
    
    // Avec paramètres
    incrementBy(amount) {
      this.count += amount
    },
    
    // Méthode async
    async fetchUsers() {
      try {
        const response = await fetch('/api/users')
        this.users = await response.json()
      } catch (error) {
        console.error('Error:', error)
      }
    },
    
    // Méthode appelant d'autres méthodes
    handleSubmit() {
      if (this.validateForm()) {
        this.saveData()
      }
    },
    
    validateForm() {
      return this.count > 0
    },
    
    saveData() {
      console.log('Saving...')
    }
  }
}

Vue 3 - Composition API

import { ref } from 'vue'

const count = ref(0)
const users = ref([])

// Fonctions
const increment = () => {
  count.value++
}

const incrementBy = (amount) => {
  count.value += amount
}

const fetchUsers = async () => {
  try {
    const response = await fetch('/api/users')
    users.value = await response.json()
  } catch (error) {
    console.error('Error:', error)
  }
}

const validateForm = () => {
  return count.value > 0
}

const handleSubmit = () => {
  if (validateForm()) {
    console.log('Saving...')
  }
}

7. Lifecycle Hooks

🔄 Correspondance Vue 2 ↔ Vue 3

Options API (Vue 2/3) Composition API (Vue 3) Quand
beforeCreate setup() Avant initialisation
created setup() Après initialisation
beforeMount onBeforeMount() Avant montage DOM
mounted onMounted() Après montage DOM
beforeUpdate onBeforeUpdate() Avant mise à jour
updated onUpdated() Après mise à jour
beforeDestroy (v2) / beforeUnmount (v3) onBeforeUnmount() Avant destruction
destroyed (v2) / unmounted (v3) onUnmounted() Après destruction
activated onActivated() KeepAlive activé
deactivated onDeactivated() KeepAlive désactivé

Exemples

<!-- Options API -->
<script>
export default {
  beforeCreate() {
    console.log('beforeCreate')
  },
  created() {
    console.log('created - fetch data here')
  },
  mounted() {
    console.log('mounted - DOM ready')
  },
  beforeUnmount() {
    console.log('beforeUnmount - cleanup')
  },
  unmounted() {
    console.log('unmounted')
  }
}
</script>

<!-- Composition API -->
<script setup>
import {
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted
} from 'vue'

// Pas de beforeCreate/created - utilisez setup()
console.log('equivalent to created')

onBeforeMount(() => {
  console.log('before mount')
})

onMounted(() => {
  console.log('mounted - DOM ready')
  // Setup event listeners
})

onBeforeUnmount(() => {
  console.log('cleanup')
})

onUnmounted(() => {
  console.log('unmounted')
})
</script>

8. Props & Events

Props - Vue 2 & 3

<!-- Parent -->
<template>
  <UserCard
    :user="currentUser"
    :loading="isLoading"
    @update="handleUpdate"
    @delete="handleDelete"
  />
</template>

<!-- UserCard.vue - Options API -->
<script>
export default {
  props: {
    user: {
      type: Object,
      required: true
    },
    loading: {
      type: Boolean,
      default: false
    }
  }
}
</script>

<!-- UserCard.vue - Script Setup -->
<script setup>
const props = defineProps({
  user: {
    type: Object,
    required: true
  },
  loading: {
    type: Boolean,
    default: false
  }
})
</script>

<!-- Avec TypeScript -->
<script setup lang="ts">
interface User {
  id: number
  name: string
  email: string
}

interface Props {
  user: User
  loading?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  loading: false
})
</script>

Events (Emits)

<!-- Child Component -->
<script setup>
// Définir les events
const emit = defineEmits(['update', 'delete'])

const handleClick = () => {
  emit('update', { id: 1, name: 'Updated' })
}

const handleDelete = (id) => {
  emit('delete', id)
}
</script>

<!-- TypeScript -->
<script setup lang="ts">
interface Emits {
  (e: 'update', value: User): void
  (e: 'delete', id: number): void
}

const emit = defineEmits<Emits>()
</script>

v-model

<!-- Vue 2 -->
<template>
  <CustomInput v-model="message" />
  <!-- Équivalent: :value="message" @input="message = $event" -->
</template>

<!-- Vue 3 - Single v-model -->
<template>
  <CustomInput v-model="message" />
  <!-- Équivalent: :model-value="message" @update:model-value="..." -->
</template>

<!-- Vue 3 - Multiple v-model -->
<template>
  <UserForm
    v-model:first-name="firstName"
    v-model:last-name="lastName"
    v-model:email="email"
  />
</template>

<!-- CustomInput.vue -->
<script setup>
const props = defineProps({
  modelValue: String
})

const emit = defineEmits(['update:modelValue'])

const updateValue = (e) => {
  emit('update:modelValue', e.target.value)
}
</script>

<template>
  <input :value="modelValue" @input="updateValue">
</template>

Provide / Inject

<!-- Ancestor Component -->
<script setup>
import { provide, ref, readonly } from 'vue'

const theme = ref('dark')
const user = ref({ name: 'John' })

// Provide
provide('theme', theme)
provide('user', readonly(user)) // readonly pour protection

// Avec Symbol (meilleure pratique)
const ThemeKey = Symbol('theme')
provide(ThemeKey, theme)
</script>

<!-- Descendant Component -->
<script setup>
import { inject } from 'vue'

// Inject
const theme = inject('theme')
const user = inject('user')

// Avec valeur par défaut
const config = inject('config', { apiUrl: '/api' })

// Avec Symbol
const ThemeKey = Symbol('theme')
const theme2 = inject(ThemeKey)
</script>

9. Composables

Qu'est-ce qu'un composable ?

Les composables sont des fonctions réutilisables qui encapsulent de la logique stateful avec la Composition API.

Exemples de composables

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

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  const double = computed(() => count.value * 2)
  
  const increment = () => count.value++
  const decrement = () => count.value--
  const reset = () => count.value = initialValue
  
  return {
    count,
    double,
    increment,
    decrement,
    reset
  }
}

// Usage
import { useCounter } from '@/composables/useCounter'

const { count, double, increment, reset } = useCounter(10)
// composables/useFetch.js
import { ref, watchEffect } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)
  const loading = ref(false)
  
  const fetchData = async () => {
    loading.value = true
    error.value = null
    
    try {
      const response = await fetch(url)
      if (!response.ok) throw new Error('Fetch failed')
      data.value = await response.json()
    } catch (e) {
      error.value = e.message
    } finally {
      loading.value = false
    }
  }
  
  watchEffect(() => {
    fetchData()
  })
  
  return { data, error, loading, refetch: fetchData }
}
// composables/useLocalStorage.js
import { ref, watch } from 'vue'

export function useLocalStorage(key, defaultValue) {
  const stored = localStorage.getItem(key)
  const value = ref(stored ? JSON.parse(stored) : defaultValue)
  
  watch(value, (newValue) => {
    localStorage.setItem(key, JSON.stringify(newValue))
  }, { deep: true })
  
  return value
}

// Usage
const settings = useLocalStorage('app-settings', {
  theme: 'dark',
  language: 'fr'
})

settings.value.theme = 'light' // Auto-saved

10. Gestion d'état (Store)

🔄 Vuex vs Pinia

Aspect Vuex (Vue 2/3) Pinia (Vue 3)
Mutations ✅ Obligatoires ❌ Pas de mutations
TypeScript Support limité ✅ Excellent
DevTools
Modularité Namespaces Stores séparés
API Options-based Composition-based
Recommandation Legacy Vue 3

Pinia (Recommandé pour Vue 3)

npm install pinia
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
createApp(App).use(pinia).mount('#app')
// stores/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useUserStore = defineStore('user', () => {
  // State
  const user = ref(null)
  const token = ref(null)
  
  // Getters
  const isAuthenticated = computed(() => !!user.value)
  const userName = computed(() => user.value?.name || 'Guest')
  
  // Actions
  const login = async (email, password) => {
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify({ email, password })
    })
    const data = await response.json()
    user.value = data.user
    token.value = data.token
  }
  
  const logout = () => {
    user.value = null
    token.value = null
  }
  
  return {
    user,
    token,
    isAuthenticated,
    userName,
    login,
    logout
  }
})
<!-- Usage dans composant -->
<script setup>
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'

const userStore = useUserStore()

// ✅ Utiliser storeToRefs pour la réactivité
const { user, isAuthenticated } = storeToRefs(userStore)

// ✅ Actions peuvent être destructurées
const { login, logout } = userStore
</script>

<template>
  <div v-if="isAuthenticated">
    Welcome {{ user.name }}!
    <button @click="logout">Logout</button>
  </div>
</template>

11. Routing (Vue Router)

npm install vue-router@4  # Vue 3
npm install vue-router@3  # Vue 2

Configuration

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'home',
    component: () => import('@/views/Home.vue')
  },
  {
    path: '/users/:id',
    name: 'user',
    component: () => import('@/views/User.vue'),
    props: true
  },
  {
    path: '/admin',
    component: () => import('@/layouts/Admin.vue'),
    children: [
      {
        path: '',
        component: () => import('@/views/Dashboard.vue')
      },
      {
        path: 'users',
        component: () => import('@/views/AdminUsers.vue')
      }
    ],
    meta: { requiresAuth: true }
  },
  {
    path: '/:pathMatch(.*)*',
    name: 'not-found',
    component: () => import('@/views/NotFound.vue')
  }
]

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

// Navigation guard
router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !isAuthenticated()) {
    next('/login')
  } else {
    next()
  }
})

export default router

Navigation

<template>
  <!-- Liens déclaratifs -->
  <router-link to="/">Home</router-link>
  <router-link :to="{ name: 'user', params: { id: 123 } }">
    User 123
  </router-link>
  
  <!-- Vue courante -->
  <router-view />
</template>

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

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

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

const goBack = () => router.back()

// Accéder aux params
console.log(route.params.id)
console.log(route.query.search)
</script>

12. Organisation du projet

src/
├── assets/                 # Images, styles, fonts
│   ├── images/
│   ├── styles/
│   └── fonts/
├── components/             # Composants réutilisables
│   ├── base/              # BaseButton, BaseInput...
│   ├── layout/            # Header, Footer, Sidebar
│   └── features/          # Composants métier
│       ├── user/
│       └── product/
├── composables/            # Logique réutilisable (Vue 3)
│   ├── useAuth.ts
│   ├── useFetch.ts
│   └── useLocalStorage.ts
├── directives/             # Directives personnalisées
│   ├── v-focus.ts
│   └── v-click-outside.ts
├── layouts/                # Layouts de pages
│   ├── DefaultLayout.vue
│   └── AdminLayout.vue
├── plugins/                # Plugins Vue
│   ├── i18n.ts
│   └── axios.ts
├── router/                 # Configuration routing
│   ├── index.ts
│   └── guards.ts
├── stores/                 # Pinia stores
│   ├── auth.ts
│   ├── user.ts
│   └── products.ts
├── types/                  # Types TypeScript
│   ├── models.ts
│   └── api.ts
├── utils/                  # Utilitaires
│   ├── formatters.ts
│   ├── validators.ts
│   └── helpers.ts
├── services/               # Services API
│   ├── api.ts
│   └── user.service.ts
├── views/                  # Pages
│   ├── Home.vue
│   ├── About.vue
│   └── users/
│       ├── UserList.vue
│       └── UserDetail.vue
├── App.vue
└── main.ts

13. Helpers & Utilities

Formatters

// utils/formatters.ts

export const formatDate = (date: Date, locale = 'fr-FR') => {
  return new Intl.DateTimeFormat(locale).format(date)
}

export const formatCurrency = (value: number, currency = 'EUR') => {
  return new Intl.NumberFormat('fr-FR', {
    style: 'currency',
    currency
  }).format(value)
}

export const formatNumber = (value: number) => {
  return new Intl.NumberFormat('fr-FR').format(value)
}

export const truncate = (str: string, length: number) => {
  return str.length > length ? str.slice(0, length) + '...' : str
}

export const capitalize = (str: string) => {
  return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()
}

Validators

// utils/validators.ts

export const isEmail = (email: string) => {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}

export const isPhone = (phone: string) => {
  return /^(?:(?:\+|00)33|0)\s*[1-9](?:[\s.-]*\d{2}){4}$/.test(phone)
}

export const isUrl = (url: string) => {
  try {
    new URL(url)
    return true
  } catch {
    return false
  }
}

export const isStrongPassword = (password: string) => {
  // Min 8 chars, 1 uppercase, 1 lowercase, 1 number, 1 special
  return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/.test(password)
}

Debounce & Throttle

// utils/debounce.ts

export function debounce<T extends (...args: any[]) => any>(
  func: T,
  wait: number
): (...args: Parameters<T>) => void {
  let timeout: ReturnType<typeof setTimeout>
  
  return function(...args: Parameters<T>) {
    clearTimeout(timeout)
    timeout = setTimeout(() => func(...args), wait)
  }
}

// Usage
const debouncedSearch = debounce((query: string) => {
  console.log('Searching:', query)
}, 300)

14. Directives personnalisées

// directives/v-focus.ts
import { Directive } from 'vue'

export const vFocus: Directive = {
  mounted(el) {
    el.focus()
  }
}

// Usage: <input v-focus>
// directives/v-click-outside.ts
export const vClickOutside: Directive = {
  mounted(el, binding) {
    el.clickOutsideEvent = (event: Event) => {
      if (!(el === event.target || el.contains(event.target as Node))) {
        binding.value(event)
      }
    }
    document.addEventListener('click', el.clickOutsideEvent)
  },
  unmounted(el) {
    document.removeEventListener('click', el.clickOutsideEvent)
  }
}

// Usage: <div v-click-outside="closeModal">...</div>

Enregistrement

// main.ts
import { vFocus } from './directives/v-focus'
import { vClickOutside } from './directives/v-click-outside'

app.directive('focus', vFocus)
app.directive('click-outside', vClickOutside)

15. Plugins & Librairies

Element Plus

npm install element-plus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

app.use(ElementPlus)

VeeValidate

npm install vee-validate yup
<script setup>
import { useForm, useField } from 'vee-validate'
import * as yup from 'yup'

const schema = yup.object({
  email: yup.string().required().email(),
  password: yup.string().required().min(8)
})

const { handleSubmit, errors } = useForm({
  validationSchema: schema
})

const { value: email } = useField('email')
const { value: password } = useField('password')

const onSubmit = handleSubmit((values) => {
  console.log('Submitted:', values)
})
</script>

<template>
  <form @submit="onSubmit">
    <input v-model="email" type="email">
    <span class="error">{{ errors.email }}</span>
    
    <input v-model="password" type="password">
    <span class="error">{{ errors.password }}</span>
    
    <button type="submit">Submit</button>
  </form>
</template>

16. Performance & Optimisation

Lazy Loading

// Composants
const HeavyComponent = defineAsyncComponent(() =>
  import('./components/HeavyComponent.vue')
)

// Routes
{
  path: '/admin',
  component: () => import('@/views/Admin.vue')
}

v-memo (Vue 3)

<template>
  <!-- Re-render seulement si item.selected change -->
  <div v-for="item in items" :key="item.id" v-memo="[item.selected]">
    {{ item.name }}
  </div>
</template>

KeepAlive

<template>
  <KeepAlive :max="10">
    <component :is="currentView" />
  </KeepAlive>
</template>

17. Testing

npm install -D vitest @vue/test-utils jsdom
// Counter.spec.ts
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import Counter from '@/components/Counter.vue'

describe('Counter', () => {
  it('renders count', () => {
    const wrapper = mount(Counter)
    expect(wrapper.text()).toContain('0')
  })
  
  it('increments on click', async () => {
    const wrapper = mount(Counter)
    await wrapper.find('button').trigger('click')
    expect(wrapper.text()).toContain('1')
  })
  
  it('emits update event', async () => {
    const wrapper = mount(Counter)
    await wrapper.find('button').trigger('click')
    expect(wrapper.emitted()).toHaveProperty('update')
  })
})

18. Best Practices & Patterns

Conventions de nommage

  • Composants: PascalCase (UserProfile.vue)
  • Composables: camelCase avec use (useAuth.ts)
  • Stores: camelCase avec use (useUserStore.ts)
  • Props: camelCase
  • Events: kebab-case

Patterns recommandés

// ✅ Bon: Composables plutôt que mixins
export function useForm() {
  // Logic réutilisable
}

// ❌ Éviter: Mixins (Vue 2 legacy)
const formMixin = {
  data() { /* ... */ }
}

// ✅ Bon: Provide/Inject pour données profondes
provide('theme', theme)

// ❌ Éviter: Props drilling
<ComponentA :theme="theme">
  <ComponentB :theme="theme">
    <ComponentC :theme="theme" />

19. Slots & Content Distribution

Slots basiques

<!-- BaseCard.vue -->
<template>
  <div class="card">
    <header>
      <slot name="header">Default Header</slot>
    </header>
    
    <main>
      <slot>Default Content</slot>
    </main>
    
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

<!-- Usage -->
<BaseCard>
  <template #header>
    <h1>Custom Header</h1>
  </template>
  
  <p>Main content</p>
  
  <template #footer>
    <button>Action</button>
  </template>
</BaseCard>

Scoped Slots

<!-- TodoList.vue -->
<template>
  <ul>
    <li v-for="(todo, index) in todos" :key="todo.id">
      <slot :todo="todo" :index="index" :toggle="toggleTodo">
        {{ todo.text }}
      </slot>
    </li>
  </ul>
</template>

<!-- Usage -->
<TodoList :todos="todos">
  <template #default="{ todo, index, toggle }">
    <span>{{ index + 1 }}. {{ todo.text }}</span>
    <button @click="toggle(todo.id)">Toggle</button>
  </template>
</TodoList>

20. Teleport & Suspense

Teleport (Vue 3)

<template>
  <button @click="showModal = true">Open Modal</button>
  
  <!-- Téléporte vers body -->
  <Teleport to="body">
    <div v-if="showModal" class="modal">
      <div class="modal-content">
        <h2>Modal Title</h2>
        <button @click="showModal = false">Close</button>
      </div>
    </div>
  </Teleport>
</template>

Suspense (Vue 3)

<template>
  <Suspense>
    <!-- Composant async -->
    <template #default>
      <AsyncComponent />
    </template>
    
    <!-- Fallback pendant chargement -->
    <template #fallback>
      <LoadingSpinner />
    </template>
  </Suspense>
</template>

<script setup>
// AsyncComponent.vue
const data = await fetch('/api/data').then(r => r.json())
</script>

21. Mixins & Composition

🔄 Mixins (Vue 2) → Composables (Vue 3)

// ❌ Mixins (Vue 2 - Legacy)
const formMixin = {
  data() {
    return {
      loading: false,
      errors: {}
    }
  },
  methods: {
    async submit() {
      this.loading = true
      try {
        await this.submitForm()
      } catch (error) {
        this.errors = error.response.data
      } finally {
        this.loading = false
      }
    }
  }
}

// Usage Vue 2
export default {
  mixins: [formMixin],
  methods: {
    async submitForm() {
      // Implementation
    }
  }
}

// ✅ Composables (Vue 3 - Recommandé)
export function useForm() {
  const loading = ref(false)
  const errors = ref({})
  
  const submit = async (submitFn) => {
    loading.value = true
    try {
      await submitFn()
    } catch (error) {
      errors.value = error.response.data
    } finally {
      loading.value = false
    }
  }
  
  return { loading, errors, submit }
}

// Usage Vue 3
const { loading, errors, submit } = useForm()

22. Render Functions & JSX

// Render function
import { h } from 'vue'

export default {
  render() {
    return h('div', { class: 'container' }, [
      h('h1', 'Hello'),
      h('p', 'World')
    ])
  }
}

// JSX (nécessite plugin)
export default {
  render() {
    return (
      <div class="container">
        <h1>Hello</h1>
        <p>World</p>
      </div>
    )
  }
}

23. Transitions & Animations

<template>
  <!-- Transition simple -->
  <Transition name="fade">
    <div v-if="show">Content</div>
  </Transition>
  
  <!-- TransitionGroup pour listes -->
  <TransitionGroup name="list" tag="ul">
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
    </li>
  </TransitionGroup>
</template>

<style>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

.list-move,
.list-enter-active,
.list-leave-active {
  transition: all 0.5s ease;
}

.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}

.list-leave-active {
  position: absolute;
}
</style>

24. Formulaires avancés

<script setup lang="ts">
import { reactive, computed } from 'vue'

const form = reactive({
  name: '',
  email: '',
  age: null,
  country: '',
  interests: [],
  newsletter: false
})

const errors = reactive({})

const validateEmail = (email: string) => {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}

const isValid = computed(() => {
  return form.name &&
         form.email &&
         validateEmail(form.email) &&
         form.country
})

const handleSubmit = () => {
  if (isValid.value) {
    console.log('Form submitted:', form)
  }
}
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <div>
      <label>Name *</label>
      <input v-model="form.name" required>
    </div>
    
    <div>
      <label>Email *</label>
      <input v-model="form.email" type="email" required>
      <span v-if="form.email && !validateEmail(form.email)" class="error">
        Invalid email
      </span>
    </div>
    
    <div>
      <label>Age</label>
      <input v-model.number="form.age" type="number">
    </div>
    
    <div>
      <label>Country *</label>
      <select v-model="form.country" required>
        <option value="">Select...</option>
        <option value="FR">France</option>
        <option value="US">USA</option>
      </select>
    </div>
    
    <div>
      <label>Interests</label>
      <label>
        <input type="checkbox" value="coding" v-model="form.interests">
        Coding
      </label>
      <label>
        <input type="checkbox" value="design" v-model="form.interests">
        Design
      </label>
    </div>
    
    <div>
      <label>
        <input type="checkbox" v-model="form.newsletter">
        Subscribe to newsletter
      </label>
    </div>
    
    <button type="submit" :disabled="!isValid">Submit</button>
  </form>
</template>

25. Internationalisation (i18n)

npm install vue-i18n@9
// plugins/i18n.ts
import { createI18n } from 'vue-i18n'

const messages = {
  en: {
    welcome: 'Welcome',
    hello: 'Hello {name}',
    items: 'No items | One item | {count} items'
  },
  fr: {
    welcome: 'Bienvenue',
    hello: 'Bonjour {name}',
    items: 'Aucun élément | Un élément | {count} éléments'
  }
}

export default createI18n({
  locale: 'fr',
  fallbackLocale: 'en',
  messages
})

// main.ts
import i18n from './plugins/i18n'
app.use(i18n)
<script setup>
import { useI18n } from 'vue-i18n'

const { t, locale } = useI18n()

const changeLocale = (lang) => {
  locale.value = lang
}
</script>

<template>
  <div>
    <p>{{ $t('welcome') }}</p>
    <p>{{ $t('hello', { name: 'John' }) }}</p>
    <p>{{ $t('items', 5) }}</p>
    
    <button @click="changeLocale('en')">EN</button>
    <button @click="changeLocale('fr')">FR</button>
  </div>
</template>

26. SSR & SSG

Nuxt.js (Framework SSR/SSG pour Vue)

npx nuxi init my-nuxt-app
cd my-nuxt-app
npm install
npm run dev

Structure Nuxt:

my-nuxt-app/
├── pages/              # Routes auto-générées
│   ├── index.vue      # → /
│   ├── about.vue      # → /about
│   └── users/
│       └── [id].vue   # → /users/:id
├── components/         # Composants
├── composables/        # Composables
├── layouts/            # Layouts
│   └── default.vue
├── server/             # API routes
│   └── api/
├── nuxt.config.ts      # Configuration
└── app.vue             # App racine

27. API & Gestion des requêtes

// services/api.ts
import axios from 'axios'

const apiClient = axios.create({
  baseURL: import.meta.env.VITE_API_URL,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json'
  }
})

// Request interceptor
apiClient.interceptors.request.use((config) => {
  const token = localStorage.getItem('token')
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
})

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

export default apiClient
// services/user.service.ts
import api from './api'

export const userService = {
  getAll: () => api.get('/users'),
  getById: (id: number) => api.get(`/users/${id}`),
  create: (data: any) => api.post('/users', data),
  update: (id: number, data: any) => api.put(`/users/${id}`, data),
  delete: (id: number) => api.delete(`/users/${id}`)
}

28. State Management avancé

Pinia Plugins

// plugins/pinia-logger.ts
export function piniaLogger({ store }) {
  store.$subscribe((mutation, state) => {
    console.log(`[${store.$id}] ${mutation.type}`, state)
  })
}

// main.ts
import { piniaLogger } from './plugins/pinia-logger'

const pinia = createPinia()
pinia.use(piniaLogger)

Persistence

// plugins/pinia-persistence.ts
export function piniaPersistence({ store }) {
  const stored = localStorage.getItem(store.$id)
  if (stored) {
    store.$patch(JSON.parse(stored))
  }
  
  store.$subscribe((mutation, state) => {
    localStorage.setItem(store.$id, JSON.stringify(state))
  })
}

29. TypeScript avancé

// types/models.ts
export interface User {
  id: number
  name: string
  email: string
  role: 'admin' | 'user'
}

export type UserRole = User['role']
export type UserId = User['id']

// Utility types
export type CreateUserDTO = Omit<User, 'id'>
export type UpdateUserDTO = Partial<User>
export type UserWithoutEmail = Omit<User, 'email'>

// Generic types
export interface ApiResponse<T> {
  data: T
  message: string
  status: number
}

export type PaginatedResponse<T> = {
  items: T[]
  total: number
  page: number
  perPage: number
}
<script setup lang="ts">
import type { User, CreateUserDTO } from '@/types/models'

interface Props {
  users: User[]
  loading?: boolean
}

interface Emits {
  (e: 'create', user: CreateUserDTO): void
  (e: 'update', id: number, user: Partial<User>): void
  (e: 'delete', id: number): void
}

const props = withDefaults(defineProps<Props>(), {
  loading: false
})

const emit = defineEmits<Emits>()

const handleCreate = (user: CreateUserDTO) => {
  emit('create', user)
}
</script>

30. Development Tools

Vue Devtools

Extension browser pour débugger les applications Vue.

Features:

  • Inspection des composants
  • Historique Vuex/Pinia
  • Events tracking
  • Performance profiling
  • Routing inspection

Vite Plugins essentiels

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

export default defineConfig({
  plugins: [
    vue(),
    
    // Auto-import des APIs Vue
    AutoImport({
      imports: ['vue', 'vue-router', 'pinia'],
      dts: 'src/auto-imports.d.ts'
    }),
    
    // Auto-import des composants
    Components({
      dirs: ['src/components'],
      extensions: ['vue'],
      dts: 'src/components.d.ts'
    })
  ]
})

31. Sécurité

XSS Prevention

<template>
  <!-- ❌ Dangereux -->
  <div v-html="userInput"></div>
  
  <!-- ✅ Sécurisé -->
  <div>{{ userInput }}</div>
  
  <!-- ✅ Avec sanitization -->
  <div v-html="sanitize(userInput)"></div>
</template>

<script setup>
import DOMPurify from 'dompurify'

const sanitize = (html: string) => {
  return DOMPurify.sanitize(html)
}
</script>

CSRF Protection

// services/api.ts
apiClient.interceptors.request.use((config) => {
  const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content
  if (csrfToken) {
    config.headers['X-CSRF-Token'] = csrfToken
  }
  return config
})

32. Accessibilité (a11y)

<template>
  <!-- Boutons accessibles -->
  <button
    type="button"
    :aria-label="label"
    :aria-pressed="isPressed"
    :aria-expanded="isExpanded"
    @click="handleClick"
  >
    {{ text }}
  </button>
  
  <!-- Navigation accessible -->
  <nav aria-label="Main navigation">
    <ul role="list">
      <li>
        <a href="/" aria-current="page">Home</a>
      </li>
    </ul>
  </nav>
  
  <!-- Formulaires accessibles -->
  <form>
    <label for="email">Email</label>
    <input
      id="email"
      type="email"
      v-model="email"
      aria-required="true"
      aria-invalid="!isEmailValid"
      aria-describedby="email-error"
    >
    <span id="email-error" role="alert" v-if="!isEmailValid">
      Invalid email
    </span>
  </form>
  
  <!-- Skip links -->
  <a href="#main-content" class="skip-link">
    Skip to main content
  </a>
</template>

33. Mobile & Progressive Web Apps

PWA Configuration

// vite.config.ts
import { VitePWA } from 'vite-plugin-pwa'

export default defineConfig({
  plugins: [
    VitePWA({
      registerType: 'autoUpdate',
      manifest: {
        name: 'My Vue App',
        short_name: 'VueApp',
        description: 'My awesome Vue application',
        theme_color: '#42b983',
        background_color: '#ffffff',
        display: 'standalone',
        icons: [
          {
            src: '/icon-192x192.png',
            sizes: '192x192',
            type: 'image/png'
          },
          {
            src: '/icon-512x512.png',
            sizes: '512x512',
            type: 'image/png'
          }
        ]
      },
      workbox: {
        runtimeCaching: [
          {
            urlPattern: /^https:\/\/api\.example\.com\/.*/i,
            handler: 'NetworkFirst',
            options: {
              cacheName: 'api-cache',
              expiration: {
                maxEntries: 10,
                maxAgeSeconds: 60 * 60 * 24 // 1 day
              }
            }
          }
        ]
      }
    })
  ]
})

34. Debugging & Troubleshooting

Common Errors

// Error: Cannot read property 'value' of undefined
// Solution: Vérifier l'initialisation des refs
const data = ref(null)
// ✅ Toujours vérifier avant d'accéder
if (data.value) {
  console.log(data.value.property)
}

// Error: Maximum call stack size exceeded
// Solution: Éviter les boucles infinies dans watch/computed
// ❌ Mauvais
const doubled = computed(() => {
  doubled.value = count.value * 2 // Boucle infinie!
})

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

// Error: Hydration mismatch (SSR)
// Solution: Utiliser <ClientOnly>
<ClientOnly>
  <ComponentWithClientOnlyData />
</ClientOnly>

Debugging Tools

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

// Debug watcher
watch(() => count.value, (newVal) => {
  console.log('Count changed:', newVal)
  debugger // Point d'arrêt
})

// Capture errors
onErrorCaptured((err, instance, info) => {
  console.error('Error captured:', err)
  console.log('Component:', instance)
  console.log('Info:', info)
  return false // Empêcher propagation
})
</script>

35. Ecosystem & Intégrations

Vue + GraphQL (Apollo)

npm install @apollo/client graphql
// plugins/apollo.ts
import { ApolloClient, InMemoryCache } from '@apollo/client'

export const apolloClient = new ApolloClient({
  uri: 'https://api.example.com/graphql',
  cache: new InMemoryCache()
})

// Usage
import { useQuery } from '@vue/apollo-composable'
import gql from 'graphql-tag'

const GET_USERS = gql`
  query GetUsers {
    users {
      id
      name
      email
    }
  }
`

const { result, loading, error } = useQuery(GET_USERS)

36. Patterns de design

Container/Presentational Pattern

<!-- UserListContainer.vue (Smart Component) -->
<script setup>
import { useUserStore } from '@/stores/user'
import UserListPresentation from './UserListPresentation.vue'

const userStore = useUserStore()
const { users, loading } = storeToRefs(userStore)
const { fetchUsers, deleteUser } = userStore

onMounted(() => {
  fetchUsers()
})
</script>

<template>
  <UserListPresentation
    :users="users"
    :loading="loading"
    @delete="deleteUser"
  />
</template>

<!-- UserListPresentation.vue (Dumb Component) -->
<script setup>
defineProps<{
  users: User[]
  loading: boolean
}>()

const emit = defineEmits<{
  (e: 'delete', id: number): void
}>()
</script>

<template>
  <div v-if="loading">Loading...</div>
  <ul v-else>
    <li v-for="user in users" :key="user.id">
      {{ user.name }}
      <button @click="emit('delete', user.id)">Delete</button>
    </li>
  </ul>
</template>

Compound Components Pattern

<!-- Accordion.vue -->
<script setup>
import { provide, ref } from 'vue'

const openItems = ref<string[]>([])

const toggle = (id: string) => {
  const index = openItems.value.indexOf(id)
  if (index > -1) {
    openItems.value.splice(index, 1)
  } else {
    openItems.value.push(id)
  }
}

provide('accordion', { openItems, toggle })
</script>

<template>
  <div class="accordion">
    <slot />
  </div>
</template>

<!-- AccordionItem.vue -->
<script setup>
import { inject, computed } from 'vue'

const props = defineProps<{ id: string }>()
const { openItems, toggle } = inject('accordion')

const isOpen = computed(() => openItems.value.includes(props.id))
</script>

<template>
  <div class="accordion-item">
    <button @click="toggle(id)">
      <slot name="title" />
    </button>
    <div v-if="isOpen" class="content">
      <slot />
    </div>
  </div>
</template>

<!-- Usage -->
<Accordion>
  <AccordionItem id="1">
    <template #title>Section 1</template>
    Content 1
  </AccordionItem>
  <AccordionItem id="2">
    <template #title>Section 2</template>
    Content 2
  </AccordionItem>
</Accordion>

37. Real-world Examples

Dashboard Complet

<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useUserStore } from '@/stores/user'

interface Stats {
  users: number
  revenue: number
  orders: number
  growth: number
}

const userStore = useUserStore()
const stats = ref<Stats>({
  users: 0,
  revenue: 0,
  orders: 0,
  growth: 0
})

const loading = ref(true)

const formattedRevenue = computed(() => {
  return new Intl.NumberFormat('fr-FR', {
    style: 'currency',
    currency: 'EUR'
  }).format(stats.value.revenue)
})

const fetchStats = async () => {
  loading.value = true
  try {
    const response = await fetch('/api/stats')
    stats.value = await response.json()
  } finally {
    loading.value = false
  }
}

onMounted(() => {
  fetchStats()
})
</script>

<template>
  <div class="dashboard">
    <header class="dashboard-header">
      <h1>Dashboard</h1>
      <p>Welcome back, {{ userStore.userName }}!</p>
    </header>
    
    <div v-if="loading" class="loading">
      Loading statistics...
    </div>
    
    <div v-else class="stats-grid">
      <div class="stat-card">
        <h3>Total Users</h3>
        <p class="stat-value">{{ stats.users.toLocaleString() }}</p>
        <span class="stat-label">Active users</span>
      </div>
      
      <div class="stat-card">
        <h3>Revenue</h3>
        <p class="stat-value">{{ formattedRevenue }}</p>
        <span class="stat-label">This month</span>
      </div>
      
      <div class="stat-card">
        <h3>Orders</h3>
        <p class="stat-value">{{ stats.orders }}</p>
        <span class="stat-label">Pending: 23</span>
      </div>
      
      <div class="stat-card">
        <h3>Growth</h3>
        <p class="stat-value">{{ stats.growth }}%</p>
        <span class="stat-label">vs last month</span>
      </div>
    </div>
  </div>
</template>

<style scoped>
.dashboard {
  padding: 2rem;
}

.dashboard-header {
  margin-bottom: 2rem;
}

.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1.5rem;
}

.stat-card {
  background: white;
  padding: 1.5rem;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.stat-value {
  font-size: 2rem;
  font-weight: bold;
  color: #42b983;
  margin: 0.5rem 0;
}

.stat-label {
  color: #666;
  font-size: 0.875rem;
}
</style>

38. Annexes

38.1 Cheat Sheet API

Réactivité

// Vue 3
ref()          // Valeur réactive
reactive()     // Objet réactif
computed()     // Propriété calculée
readonly()     // Lecture seule
watch()        // Watcher
watchEffect()  // Watcher automatique

Lifecycle

onBeforeMount()
onMounted()
onBeforeUpdate()
onUpdated()
onBeforeUnmount()
onUnmounted()
onActivated()
onDeactivated()

Composant

defineProps()
defineEmits()
defineExpose()
withDefaults()

38.2 Migration Vue 2 → Vue 3

Vue 2 Vue 3 Migration
new Vue() createApp() Remplacer la création
beforeDestroy beforeUnmount Renommer
destroyed unmounted Renommer
$on/$off/$once ❌ Retiré Utiliser mitt
Filters ❌ Retirés Utiliser methods/computed
$children ❌ Retiré Utiliser refs
$set/$delete ❌ Plus nécessaire Direct avec Proxy

38.3 Ressources Utiles

Documentation Officielle:

Outils:

Communauté:


🎉 Fin de la documentation complète Vue.js 2 & 3

Cette documentation couvre tous les aspects essentiels et avancés de Vue.js pour vous accompagner dans vos projets, du débutant à l'expert.


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment