Migrer vers la Composition API Vue 3 : Guide Etape par Etape

Migrez vos composants Vue vers la Composition API. useStore, ref, onMounted et lifecycle hooks expliques avec des exemples pratiques.

Mahmoud DEVO
Mahmoud DEVO
December 28, 2025 9 min read
Migrer vers la Composition API Vue 3 : Guide Etape par Etape
Table of Contents

Migrer vers la Composition API Vue 3 : Guide Complet

L’arrivee de Vue 3 marque un tournant majeur dans l’ecosysteme Vue.js avec l’introduction de la Composition API. Cette nouvelle approche revolutionne la facon dont nous organisons et reutilisons la logique dans nos composants. Dans ce guide exhaustif, nous allons explorer en profondeur comment migrer vos composants existants vers la Composition API, comprendre ses avantages, et maitriser toutes ses fonctionnalites.

Introduction : Options API vs Composition API

L’Options API : L’approche traditionnelle

Depuis Vue 2, l’Options API a ete la methode standard pour creer des composants. Cette approche organise le code par type d’option : data, methods, computed, watch, etc.

// Options API - Vue 2/3
export default {
  data() {
    return {
      count: 0,
      user: null,
      isLoading: false
    }
  },
  computed: {
    doubleCount() {
      return this.count * 2
    }
  },
  methods: {
    increment() {
      this.count++
    },
    async fetchUser() {
      this.isLoading = true
      this.user = await api.getUser()
      this.isLoading = false
    }
  },
  mounted() {
    this.fetchUser()
  }
}

Problemes de l’Options API :

  1. Fragmentation de la logique : Le code lie a une meme fonctionnalite est disperse entre data, methods, computed, etc.
  2. Difficulte de reutilisation : Les mixins, la solution historique, causent des conflits de noms et une origine obscure des proprietes
  3. Typage TypeScript limite : Le contexte this complique l’inference de types
  4. Composants volumineux : Les gros composants deviennent difficiles a maintenir

La Composition API : Une nouvelle philosophie

La Composition API permet d’organiser le code par fonctionnalite logique plutot que par type d’option :

// Composition API - Vue 3
import { ref, computed, onMounted } from 'vue'

export default {
  setup() {
    // Logique du compteur
    const count = ref(0)
    const doubleCount = computed(() => count.value * 2)
    const increment = () => count.value++

    // Logique de l'utilisateur
    const user = ref(null)
    const isLoading = ref(false)

    const fetchUser = async () => {
      isLoading.value = true
      user.value = await api.getUser()
      isLoading.value = false
    }

    onMounted(() => {
      fetchUser()
    })

    return { count, doubleCount, increment, user, isLoading }
  }
}

Avantages de la Composition API :

  1. Code organise par fonctionnalite : Toute la logique liee est regroupee
  2. Reutilisation facile : Les composables remplacent avantageusement les mixins
  3. Excellent support TypeScript : Inference de types naturelle
  4. Meilleure testabilite : Les fonctions pures sont faciles a tester
  5. Tree-shaking optimal : Seules les fonctions importees sont incluses dans le bundle

Section 1 : useStore() et l’acces au store Vuex

Comprendre useStore()

Dans la Composition API, l’acces au store Vuex se fait via la fonction useStore(). Cette fonction doit etre appelee dans le contexte de setup().

import { useStore } from 'vuex'

export default {
  setup() {
    const store = useStore()

    // Acces au state
    const users = computed(() => store.state.users)

    // Acces aux getters
    const activeUsers = computed(() => store.getters.activeUsers)

    // Dispatch d'actions
    const fetchUsers = () => store.dispatch('fetchUsers')

    // Commit de mutations
    const addUser = (user) => store.commit('ADD_USER', user)

    return { users, activeUsers, fetchUsers, addUser }
  }
}

Pattern avance : Helpers pour Vuex

Pour simplifier l’utilisation du store, creez des helpers reutilisables :

// composables/useVuex.js
import { computed } from 'vue'
import { useStore } from 'vuex'

export function useState(keys) {
  const store = useStore()
  const state = {}

  keys.forEach(key => {
    state[key] = computed(() => store.state[key])
  })

  return state
}

export function useGetters(keys) {
  const store = useStore()
  const getters = {}

  keys.forEach(key => {
    getters[key] = computed(() => store.getters[key])
  })

  return getters
}

export function useActions(keys) {
  const store = useStore()
  const actions = {}

  keys.forEach(key => {
    actions[key] = (payload) => store.dispatch(key, payload)
  })

  return actions
}

Utilisation dans un composant :

import { useState, useGetters, useActions } from '@/composables/useVuex'

export default {
  setup() {
    const { users, products } = useState(['users', 'products'])
    const { totalUsers, activeProducts } = useGetters(['totalUsers', 'activeProducts'])
    const { fetchUsers, updateProduct } = useActions(['fetchUsers', 'updateProduct'])

    return { users, products, totalUsers, activeProducts, fetchUsers, updateProduct }
  }
}

Migration de Pinia (alternative moderne)

Si vous envisagez une migration vers Pinia (le successeur officiel de Vuex), la syntaxe est encore plus simple :

import { useUserStore } from '@/stores/user'

export default {
  setup() {
    const userStore = useUserStore()

    // Acces direct aux proprietes reactives
    // userStore.users, userStore.fetchUsers(), etc.

    return { userStore }
  }
}

Section 2 : ref() vs reactive() - Comprendre la reactivite

ref() : Pour les valeurs primitives et les objets

La fonction ref() cree une reference reactive. Elle fonctionne avec tous les types de valeurs.

import { ref } from 'vue'

// Primitives
const count = ref(0)
const name = ref('John')
const isActive = ref(true)

// Objets et tableaux
const user = ref({ name: 'John', age: 30 })
const items = ref([1, 2, 3])

// Acces et modification via .value
console.log(count.value) // 0
count.value++
console.log(count.value) // 1

// Dans le template, .value est automatiquement "unwrap"
// <template>
//   <span>{{ count }}</span> <!-- pas besoin de count.value -->
// </template>

reactive() : Pour les objets complexes

La fonction reactive() cree un proxy reactif. Elle ne fonctionne qu’avec les objets.

import { reactive } from 'vue'

const state = reactive({
  count: 0,
  user: {
    name: 'John',
    email: 'john@example.com'
  },
  items: []
})

// Acces direct (pas de .value)
console.log(state.count) // 0
state.count++

// Modification des proprietes imbriquees
state.user.name = 'Jane'
state.items.push({ id: 1 })

Tableau comparatif ref() vs reactive()

Critereref()reactive()
Types supportesTous (primitifs, objets, tableaux)Objets uniquement
Acces a la valeurVia .valueDirect
DestructurationPerd la reactivite si on extrait .valuePerd la reactivite
Remplacement completref.value = newValueNon recommande
TypeScriptMeilleure inferenceBonne inference
Cas d’usageValeurs simples, objets remplaceablesObjets complexes stables

toRef() et toRefs() : Conserver la reactivite

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

const state = reactive({
  count: 0,
  name: 'John'
})

// toRef : creer une ref liee a une propriete reactive
const countRef = toRef(state, 'count')
countRef.value++ // state.count est aussi incremente

// toRefs : convertir toutes les proprietes en refs
const { count, name } = toRefs(state)
// count et name sont maintenant des refs liees a state

Quand utiliser quoi ?

// Utilisez ref() pour :
const isLoading = ref(false)        // Booleens
const errorMessage = ref('')         // Strings
const selectedId = ref(null)         // Valeurs nullables
const userData = ref(null)           // Objets qui seront remplaces

// Utilisez reactive() pour :
const formState = reactive({         // Formulaires complexes
  firstName: '',
  lastName: '',
  email: '',
  preferences: {
    newsletter: true,
    notifications: false
  }
})

const filters = reactive({           // Etats de filtrage
  search: '',
  category: 'all',
  sortBy: 'date',
  order: 'desc'
})

Section 3 : Les Lifecycle Hooks

Vue d’ensemble des hooks

La Composition API fournit des fonctions equivalentes a tous les hooks de l’Options API :

Options APIComposition API
beforeCreateNon necessaire (dans setup)
createdNon necessaire (dans setup)
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
activatedonActivated
deactivatedonDeactivated

onMounted : Initialisation apres le rendu

import { ref, onMounted } from 'vue'

export default {
  setup() {
    const data = ref(null)
    const element = ref(null) // template ref

    onMounted(async () => {
      // Le DOM est maintenant disponible
      console.log(element.value) // Element DOM

      // Chargement de donnees initial
      data.value = await fetchData()

      // Initialisation de librairies tierces
      initChart(element.value)
    })

    return { data, element }
  }
}

onUpdated : Reagir aux mises a jour du DOM

import { ref, onUpdated } from 'vue'

export default {
  setup() {
    const items = ref([])

    onUpdated(() => {
      // Appele apres chaque mise a jour du DOM
      console.log('Le DOM a ete mis a jour')

      // Attention : peut etre appele frequemment
      // Utilisez avec parcimonie
    })

    return { items }
  }
}

onUnmounted : Nettoyage des ressources

import { ref, onMounted, onUnmounted } from 'vue'

export default {
  setup() {
    const intervalId = ref(null)
    const socketConnection = ref(null)

    onMounted(() => {
      // Demarrer un intervalle
      intervalId.value = setInterval(() => {
        console.log('tick')
      }, 1000)

      // Etablir une connexion WebSocket
      socketConnection.value = new WebSocket('ws://example.com')
    })

    onUnmounted(() => {
      // IMPORTANT : Nettoyer pour eviter les fuites memoire
      if (intervalId.value) {
        clearInterval(intervalId.value)
      }

      if (socketConnection.value) {
        socketConnection.value.close()
      }
    })

    return {}
  }
}

Pattern : Hook de nettoyage automatique

// composables/useInterval.js
import { onUnmounted } from 'vue'

export function useInterval(callback, delay) {
  const intervalId = setInterval(callback, delay)

  // Nettoyage automatique lors du demontage
  onUnmounted(() => {
    clearInterval(intervalId)
  })

  return intervalId
}

// Utilisation
import { useInterval } from '@/composables/useInterval'

export default {
  setup() {
    // L'intervalle sera automatiquement nettoye
    useInterval(() => {
      console.log('tick')
    }, 1000)

    return {}
  }
}

Section 4 : watch() et watchEffect()

watch() : Observation explicite

La fonction watch() permet d’observer des sources reactives specifiques :

import { ref, watch } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const user = ref({ name: 'John' })

    // Observer une ref
    watch(count, (newValue, oldValue) => {
      console.log(`count: ${oldValue} -> ${newValue}`)
    })

    // Observer avec options
    watch(count, (newValue) => {
      console.log('count changed:', newValue)
    }, {
      immediate: true,  // Executer immediatement
      deep: false,      // Observation profonde (pour objets)
      flush: 'post'     // Timing: 'pre', 'post', 'sync'
    })

    // Observer une propriete d'objet (fonction getter)
    watch(
      () => user.value.name,
      (newName) => {
        console.log('name changed:', newName)
      }
    )

    // Observer plusieurs sources
    watch(
      [count, () => user.value.name],
      ([newCount, newName], [oldCount, oldName]) => {
        console.log('Multiple values changed')
      }
    )

    return { count, user }
  }
}

watchEffect() : Observation automatique

watchEffect() detecte automatiquement les dependances reactives :

import { ref, watchEffect } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const multiplier = ref(2)

    // Les dependances sont detectees automatiquement
    watchEffect(() => {
      // Cette fonction sera re-executee quand count OU multiplier change
      console.log(`Result: ${count.value * multiplier.value}`)
    })

    // watchEffect avec cleanup
    watchEffect((onCleanup) => {
      const controller = new AbortController()

      fetch('/api/data', { signal: controller.signal })
        .then(r => r.json())
        .then(data => {
          // traiter les donnees
        })

      // Cleanup avant chaque re-execution
      onCleanup(() => {
        controller.abort()
      })
    })

    return { count, multiplier }
  }
}

Arreter un watcher

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

export default {
  setup() {
    const data = ref(null)

    // watch() et watchEffect() retournent une fonction d'arret
    const stopWatch = watch(data, (newVal) => {
      console.log('data changed')
    })

    const stopEffect = watchEffect(() => {
      console.log(data.value)
    })

    // Arreter les watchers manuellement
    const stopAll = () => {
      stopWatch()
      stopEffect()
    }

    return { data, stopAll }
  }
}

Comparaison watch() vs watchEffect()

Aspectwatch()watchEffect()
DependancesExplicitesAutomatiques
Valeur precedenteDisponibleNon disponible
Execution initialeNon (sauf immediate: true)Oui
Cas d’usageReactions specifiquesEffets de bord generaux

Section 5 : computed() dans la Composition API

Les bases de computed()

import { ref, computed } from 'vue'

export default {
  setup() {
    const firstName = ref('John')
    const lastName = ref('Doe')

    // Computed en lecture seule
    const fullName = computed(() => {
      return `${firstName.value} ${lastName.value}`
    })

    // Computed avec getter et setter
    const fullNameWritable = computed({
      get() {
        return `${firstName.value} ${lastName.value}`
      },
      set(newValue) {
        const parts = newValue.split(' ')
        firstName.value = parts[0]
        lastName.value = parts[1] || ''
      }
    })

    return { firstName, lastName, fullName, fullNameWritable }
  }
}

Computed avec dependances complexes

import { ref, computed } from 'vue'

export default {
  setup() {
    const items = ref([
      { id: 1, name: 'Apple', price: 1.5, category: 'fruits' },
      { id: 2, name: 'Bread', price: 2.0, category: 'bakery' },
      { id: 3, name: 'Milk', price: 1.0, category: 'dairy' }
    ])

    const searchQuery = ref('')
    const selectedCategory = ref('all')
    const sortBy = ref('name')

    // Computed chaine : filtrage puis tri
    const filteredItems = computed(() => {
      return items.value.filter(item => {
        const matchesSearch = item.name
          .toLowerCase()
          .includes(searchQuery.value.toLowerCase())

        const matchesCategory = selectedCategory.value === 'all'
          || item.category === selectedCategory.value

        return matchesSearch && matchesCategory
      })
    })

    const sortedItems = computed(() => {
      return [...filteredItems.value].sort((a, b) => {
        if (sortBy.value === 'name') {
          return a.name.localeCompare(b.name)
        }
        return a.price - b.price
      })
    })

    // Statistiques derivees
    const totalPrice = computed(() => {
      return sortedItems.value.reduce((sum, item) => sum + item.price, 0)
    })

    const itemCount = computed(() => sortedItems.value.length)

    return {
      items,
      searchQuery,
      selectedCategory,
      sortBy,
      sortedItems,
      totalPrice,
      itemCount
    }
  }
}

Section 6 : Les Composables - Fonctions Reutilisables

Qu’est-ce qu’un composable ?

Un composable est une fonction qui encapsule une logique reutilisable en utilisant la Composition API. C’est le remplacement moderne des mixins.

Exemple : useCounter

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

export function useCounter(initialValue = 0, step = 1) {
  const count = ref(initialValue)

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

  const increment = () => count.value += step
  const decrement = () => count.value -= step
  const reset = () => count.value = initialValue

  return {
    count,
    doubleCount,
    isPositive,
    increment,
    decrement,
    reset
  }
}

Exemple : useFetch

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

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)
  const isLoading = ref(false)

  const fetchData = async () => {
    isLoading.value = true
    error.value = null

    try {
      const response = await fetch(toValue(url))

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }

      data.value = await response.json()
    } catch (e) {
      error.value = e.message
    } finally {
      isLoading.value = false
    }
  }

  // Re-fetch automatiquement si l'URL change
  watchEffect(() => {
    if (toValue(url)) {
      fetchData()
    }
  })

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

Exemple : useLocalStorage

// composables/useLocalStorage.js
import { ref, watch } from 'vue'

export function useLocalStorage(key, defaultValue) {
  // Lire la valeur initiale
  const storedValue = localStorage.getItem(key)
  const value = ref(
    storedValue ? JSON.parse(storedValue) : defaultValue
  )

  // Persister les changements
  watch(value, (newValue) => {
    if (newValue === null || newValue === undefined) {
      localStorage.removeItem(key)
    } else {
      localStorage.setItem(key, JSON.stringify(newValue))
    }
  }, { deep: true })

  return value
}

Exemple : useDebounce

// composables/useDebounce.js
import { ref, watch } from 'vue'

export function useDebounce(value, delay = 300) {
  const debouncedValue = ref(value.value)
  let timeout

  watch(value, (newValue) => {
    clearTimeout(timeout)
    timeout = setTimeout(() => {
      debouncedValue.value = newValue
    }, delay)
  })

  return debouncedValue
}

// Utilisation
import { ref } from 'vue'
import { useDebounce } from '@/composables/useDebounce'

export default {
  setup() {
    const searchInput = ref('')
    const debouncedSearch = useDebounce(searchInput, 500)

    // debouncedSearch ne changera que 500ms apres
    // la derniere modification de searchInput

    return { searchInput, debouncedSearch }
  }
}

Section 7 : Exemple Complet de Migration

Composant Options API (Avant)

// UserProfile.vue - Options API
export default {
  name: 'UserProfile',

  props: {
    userId: {
      type: Number,
      required: true
    }
  },

  data() {
    return {
      user: null,
      posts: [],
      isLoading: false,
      error: null,
      showDetails: false
    }
  },

  computed: {
    fullName() {
      return this.user
        ? `${this.user.firstName} ${this.user.lastName}`
        : ''
    },
    postCount() {
      return this.posts.length
    },
    recentPosts() {
      return this.posts.slice(0, 5)
    }
  },

  watch: {
    userId: {
      handler: 'fetchUser',
      immediate: true
    }
  },

  methods: {
    async fetchUser() {
      this.isLoading = true
      this.error = null

      try {
        const response = await fetch(`/api/users/${this.userId}`)
        this.user = await response.json()
        await this.fetchPosts()
      } catch (e) {
        this.error = e.message
      } finally {
        this.isLoading = false
      }
    },

    async fetchPosts() {
      const response = await fetch(`/api/users/${this.userId}/posts`)
      this.posts = await response.json()
    },

    toggleDetails() {
      this.showDetails = !this.showDetails
    }
  },

  mounted() {
    console.log('UserProfile mounted')
  },

  unmounted() {
    console.log('UserProfile unmounted')
  }
}

Composant Composition API (Apres)

<!-- UserProfile.vue - Composition API -->
<script setup>
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'

// Props
const props = defineProps({
  userId: {
    type: Number,
    required: true
  }
})

// State reactif
const user = ref(null)
const posts = ref([])
const isLoading = ref(false)
const error = ref(null)
const showDetails = ref(false)

// Computed
const fullName = computed(() => {
  return user.value
    ? `${user.value.firstName} ${user.value.lastName}`
    : ''
})

const postCount = computed(() => posts.value.length)

const recentPosts = computed(() => posts.value.slice(0, 5))

// Methodes
const fetchUser = async () => {
  isLoading.value = true
  error.value = null

  try {
    const response = await fetch(`/api/users/${props.userId}`)
    user.value = await response.json()
    await fetchPosts()
  } catch (e) {
    error.value = e.message
  } finally {
    isLoading.value = false
  }
}

const fetchPosts = async () => {
  const response = await fetch(`/api/users/${props.userId}/posts`)
  posts.value = await response.json()
}

const toggleDetails = () => {
  showDetails.value = !showDetails.value
}

// Watcher
watch(
  () => props.userId,
  () => fetchUser(),
  { immediate: true }
)

// Lifecycle hooks
onMounted(() => {
  console.log('UserProfile mounted')
})

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

Version avec Composables (Optimale)

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

export function useUser(userId) {
  const user = ref(null)
  const isLoading = ref(false)
  const error = ref(null)

  const fullName = computed(() => {
    return user.value
      ? `${user.value.firstName} ${user.value.lastName}`
      : ''
  })

  const fetchUser = async () => {
    isLoading.value = true
    error.value = null

    try {
      const response = await fetch(`/api/users/${userId.value}`)
      user.value = await response.json()
    } catch (e) {
      error.value = e.message
    } finally {
      isLoading.value = false
    }
  }

  watch(userId, fetchUser, { immediate: true })

  return { user, fullName, isLoading, error, refetch: fetchUser }
}

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

export function usePosts(userId) {
  const posts = ref([])

  const postCount = computed(() => posts.value.length)
  const recentPosts = computed(() => posts.value.slice(0, 5))

  const fetchPosts = async () => {
    const response = await fetch(`/api/users/${userId.value}/posts`)
    posts.value = await response.json()
  }

  return { posts, postCount, recentPosts, fetchPosts }
}
<!-- UserProfile.vue - Avec Composables -->
<script setup>
import { ref, toRef, onMounted, onUnmounted } from 'vue'
import { useUser } from '@/composables/useUser'
import { usePosts } from '@/composables/usePosts'

const props = defineProps({
  userId: {
    type: Number,
    required: true
  }
})

const userIdRef = toRef(props, 'userId')

// Utilisation des composables
const { user, fullName, isLoading, error } = useUser(userIdRef)
const { posts, postCount, recentPosts, fetchPosts } = usePosts(userIdRef)

// State local
const showDetails = ref(false)
const toggleDetails = () => showDetails.value = !showDetails.value

// Lifecycle
onMounted(() => {
  fetchPosts()
  console.log('UserProfile mounted')
})

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

Section 8 : Tableau Comparatif Complet

AspectOptions APIComposition API
Organisation du codePar type d’optionPar fonctionnalite
ReutilisationMixins (problematique)Composables (elegant)
Support TypeScriptLimiteExcellent
Tree-shakingTout est inclusImports selectifs
Courbe d’apprentissageDouceModeree
LisibiliteBonne pour petits composantsExcellente pour grands composants
TestabiliteNecessite mountFonctions pures testables
Acces a thisOuiNon (pas necessaire)
Reactivity refsImplicite via data()Explicite via ref()/reactive()
Lifecycle hooksOptions dans l’objetFonctions importees

Section 9 : Bonnes Pratiques

1. Preferez <script setup> pour les nouveaux composants

<!-- Recommande -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>

<!-- Moins concis -->
<script>
import { ref } from 'vue'
export default {
  setup() {
    const count = ref(0)
    return { count }
  }
}
</script>

2. Nommez vos composables avec le prefixe use

// Bon
export function useUser() { }
export function useFetch() { }
export function useLocalStorage() { }

// Mauvais
export function getUser() { }
export function fetchData() { }
export function storage() { }

3. Retournez toujours un objet depuis vos composables

// Bon - permet la destructuration
export function useCounter() {
  const count = ref(0)
  const increment = () => count.value++

  return { count, increment }
}

// Utilisation flexible
const { count } = useCounter()
const counter = useCounter()

4. Gerez toujours le nettoyage dans onUnmounted

export function useEventListener(target, event, callback) {
  onMounted(() => {
    target.addEventListener(event, callback)
  })

  // IMPORTANT : toujours nettoyer
  onUnmounted(() => {
    target.removeEventListener(event, callback)
  })
}

5. Utilisez toRef/toRefs pour les props dans les composables

// Dans le composant
const props = defineProps(['userId'])
const { user } = useUser(toRef(props, 'userId'))

// Dans le composable
export function useUser(userId) {
  // userId reste reactif meme si c'est une prop
  watch(userId, () => fetchUser())
}

6. Evitez les effets de bord dans les computed

// Mauvais - effet de bord dans computed
const total = computed(() => {
  console.log('Calculating...')  // Effet de bord
  localStorage.setItem('total', sum)  // Effet de bord
  return items.value.reduce((a, b) => a + b, 0)
})

// Bon - computed pur
const total = computed(() => {
  return items.value.reduce((a, b) => a + b, 0)
})

// Utilisez watch pour les effets de bord
watch(total, (newTotal) => {
  console.log('Total changed:', newTotal)
  localStorage.setItem('total', newTotal)
})

Section 10 : Pieges Courants a Eviter

1. Oublier .value avec ref()

// Erreur courante
const count = ref(0)
count++  // Ne fonctionne pas !
console.log(count)  // Affiche l'objet Ref, pas la valeur

// Correct
count.value++
console.log(count.value)

2. Destructurer reactive() perd la reactivite

const state = reactive({ count: 0, name: 'John' })

// MAUVAIS - perd la reactivite
const { count, name } = state
count++  // Ne met pas a jour state.count !

// BON - utiliser toRefs
const { count, name } = toRefs(state)
count.value++  // Met a jour state.count

3. Appeler des composables en dehors de setup()

// MAUVAIS - ne fonctionne pas
const handleClick = () => {
  const { data } = useFetch('/api/data')  // Erreur !
}

// BON - appeler dans setup
const { data, refetch } = useFetch('/api/data')

const handleClick = () => {
  refetch()  // Utiliser la fonction retournee
}

4. Creer des watchers infinis

// MAUVAIS - boucle infinie
const count = ref(0)
watch(count, () => {
  count.value++  // Declenche le watcher encore !
})

// BON - utiliser une condition
watch(count, (newVal) => {
  if (newVal < 10) {
    count.value++
  }
})

5. Ne pas typer correctement avec TypeScript

// MAUVAIS - type any implicite
const user = ref(null)

// BON - typage explicite
interface User {
  id: number
  name: string
  email: string
}

const user = ref<User | null>(null)

Conclusion

La migration vers la Composition API de Vue 3 represente une evolution majeure dans la facon dont nous structurons nos applications Vue. En adoptant cette nouvelle approche, vous beneficiez de :

  • Une meilleure organisation du code grace au regroupement logique
  • Une reutilisabilite accrue avec les composables
  • Un support TypeScript natif pour des applications plus robustes
  • Une performance optimisee grace au tree-shaking
  • Une maintenance simplifiee pour les projets de grande envergure

La cle d’une migration reussie reside dans une approche progressive : commencez par les nouveaux composants, puis migrez progressivement les composants existants en utilisant les patterns et composables presentes dans ce guide.

N’oubliez pas que l’Options API reste parfaitement valide dans Vue 3. La Composition API est une option supplementaire, pas un remplacement obligatoire. Choisissez l’approche qui convient le mieux a votre equipe et a votre projet.


Ressources Complementaires

La maitrise de la Composition API ouvre la porte a des applications Vue plus maintenables, testables et performantes. Prenez le temps d’experimenter avec les differents concepts presentes ici, et n’hesitez pas a creer vos propres composables pour encapsuler la logique metier de votre application.

Advertisement

In-Article Ad

Dev Mode

Share this article

Mahmoud DEVO

Mahmoud DEVO

Senior Full-Stack Developer

I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.

Enjoyed this article?

Subscribe to get more tech content delivered to your inbox.

Related Articles