Les composables

Introduction

Un composable est une fonction JavaScript qui regroupe de la logique réactive afin de pouvoir la réutiliser dans plusieurs composants. Il sert à organiser du code qui reviendrait souvent, comme la gestion d'un compteur, d'un formulaire ou d'un écouteur de clavier.

À l'intérieur d'un composable, on peut créer des variables réactives avec ref() ou reactive(), définir des valeurs calculées avec computed(), observer des changements avec watch() ou même utiliser des hooks de cycle de vie. Le tout reste du code JavaScript standard.

L'objectif est de séparer la logique du composant de son interface visuelle. Cela rend le code plus clair, plus facile à tester et à entretenir, tout en évitant de répéter les mêmes morceaux de code dans plusieurs composants.

Les éléments qui ne sont pas retournés restent internes au composable. Lui seul peut y accéder, tandis que le composant qui l'utilise ne voit que ce qui est retourné. Ces valeurs et fonctions retournées forment ce qu'on appelle l'API du composable.

Chaque fois qu'un composant appelle le composable, il obtient sa propre version indépendante des données. C'est ce qu'on appelle un état isolé, créé grâce aux fermetures JavaScript.

En général, on retourne un objet contenant à la fois les valeurs réactives et les fonctions d'action. Cette forme est simple à utiliser et à comprendre.

Exemples typiques

Créer et utiliser un composable

Pour créer un composable, on écrit une fonction JavaScript ordinaire dans un fichier dédié. Cette fonction utilise les outils réactifs de Vue pour gérer un petit ensemble de données ou de comportements réutilisables.

À l'intérieur du composable, on choisit les éléments que l'on souhaite rendre accessibles au composant à l'aide d'un return. Le composable peut ensuite être partagé grâce au système standard de modules JavaScript (on l'exporte avec export et on l'importe dans le composant qui en a besoin avec import).

Exemple : Compteur réutilisable

L'exemple suivant montre comment un même composable peut être utilisé dans plusieurs composants différents. Il illustre la manière dont une logique réactive peut être partagée tout en gardant des instances indépendantes, chacune avec son propre comportement et sa propre interface.

Composable useCompteur (src/composables/useCompteur.js)


                import { ref } from 'vue';

                // Chaque appel de cette fonction crée un nouvel état réactif indépendant.
                // Les données sont déclarées à l'intérieur de la fonction, pas au niveau module.
                export function useCompteur()
                {
                    const initial = 0;
                    const count = ref(initial);

                    const incrementer = () => { count.value++ };
                    const decrementer = () => { count.value-- };
                    const reinitialiser = () => { count.value = initial };

                    // On expose uniquement ce qui est utile aux composants.
                    // Format objet pour permettre la déstructuration côté composant.
                    return { count, incrementer, decrementer, reinitialiser };
                };
            

Composant enfant n°1 DemoCompteur1 (src/components/DemoCompteur1.vue)


                <script setup>
                import { useCompteur } from '../composables/useCompteur.js'

                // Déstructuration directe des éléments exposés par le composable.
                // Cette instance aura son propre état indépendant.
                const { count, incrementer, decrementer, reinitialiser } = useCompteur()
                </script>

                <template>
                    <h3>Composant : Version 01</h3>
                    <div>
                        <p>Valeur actuelle {{ count }}</p>
                        <button @click="incrementer">+1</button>
                        <button @click="decrementer">-1</button>
                        <button @click="reinitialiser">Réinitialiser</button>
                    </div>
                </template>
            

Composant enfant n°2 DemoCompteur2 (src/components/DemoCompteur2.vue)


                <script setup>
                import { useCompteur } from '../composables/useCompteur.js';

                // Même logique, autre présentation.
                // Ce composant utilise sa propre instance du composable, donc un autre état.
                const { count, incrementer, decrementer, reinitialiser } = useCompteur();
                </script>

                <template>
                    <h3>Composant : Version 02</h3>
                    <div>
                        <button @click="decrementer"><b>-</b></button>
                        <button @click="incrementer"><b>+</b></button>
                        <button @click="reinitialiser">Reset</button>
                        <output>Résultat : {{ count }}</output>
                    </div>
                </template>

                <style scoped>
                    div {
                        display: flex;
                        align-items: center;
                        gap: 1rem;
                    }

                    button {
                        border-radius: 50%;
                        width: 3rem;
                        height: 3rem;
                        display: inline-flex;
                        justify-content: center;
                        align-items: center;
                    }
                </style>
            

Composant racine App.vue (src/App.vue)


                <script setup>
                import DemoCompteur1 from './components/DemoCompteur1.vue';
                import DemoCompteur2 from './components/DemoCompteur2.vue';
                </script>

                <template>
                    <main>
                        <h2>Même logique réactive, mais structure, style et état propres à chaque composant</h2>
                        <DemoCompteur1 />
                        <hr />
                        <DemoCompteur2 />
                    </main>
                </template>
            

Les deux composants réutilisent la même logique d'état et d'actions fournie par le composable, tout en gardant une présentation propre à chacun. Comme les données réactives sont déclarées à l'intérieur du composable, chaque appel crée un état isolé et indépendant. Les deux compteurs fonctionnent donc séparément, sans interférence entre eux.

Conventions pour les composables

Dans l'écosystème Vue, on suit quelques conventions simples pour que les composables restent faciles à reconnaître et à retrouver.

Nommage des fonctions  Un composable est une fonction JavaScript dont le nom commence par use et décrit ce que la logique fait. (ex. : useCompteur(), useClavier(), useTodolistLocalStorage()). Ce préfixe signale que la fonction contient de la logique réactive et qu'elle est pensée pour être utilisée dans des composants Vue.

Nommage des fichiers  On place en général chaque composable dans un fichier qui porte le même nom que la fonction. (ex. : useCompteur() est déclaré dans le fichier src/composables/useCompteur.js).

Dossier dédié  Les composables sont rangés dans un dossier src/composables. Cela les sépare clairement des composants (src/components) et d'autres utilitaires non réactifs qui peuvent aller dans src/utils ou src/lib.

L'idée générale est simple, Si une fonction utilise l'API de composition de Vue (ref, computed, watch, hooks, etc.) et encapsule une logique réactive réutilisable, on la place dans un composable nommé avec le préfixe use déclaré dans un fichier .js du même nom, lui même situé dans le dossier src/composables.

Rendre un composable paramétrable

Un composable peut recevoir des arguments afin d'adapter son comportement selon le contexte dans lequel il est utilisé. On peut lui passer une valeur directe, ou un objet d'options lorsque plusieurs paramètres sont possibles. Cette approche rend le composable plus souple et réutilisable dans différentes situations.

Exemple : Options avec valeurs par défaut

Composable useCompteur (src/composables/useCompteur.js) :


                import { ref } from 'vue';

                // Le composable accepte un objet d'options facultatif.
                export function useCompteur(options = {})
                {
                    // On utilise la déstructuration avec des valeurs par défaut
                    // pour rendre l'appel plus souple et clair.
                    const { initial = 0, pas = 1 } = options;

                    const count = ref(initial);
                    const plus = () => { count.value += pas };
                    const moins = () => { count.value -= pas };

                    return { count, plus, moins };
                }
            

Utilisation dans le composant DemoCompteur (src/components/DemoCompteur.vue)


                <script setup>
                import { useCompteur } from '../composables/useCompteur.js'

                // On passe un objet d'options pour personnaliser le comportement.
                // Ici, le compteur démarre à 5 et s'incrémente de 2 à chaque clic.
                const { count, plus, moins, reinitialiser } = useCompteur({ initial: 5, pas: 2 })
                </script>

                <template>
                    <div class="compteur">
                        <p>Valeur actuelle {{ count }}</p>
                        <button @click="plus">+</button>
                        <button @click="moins">-</button>
                        <button @click="reinitialiser">Réinitialiser</button>
                    </div>
                </template>
            

Ce modèle rend le composable souple et réutilisable. Les paramètres sont nommés, donc leur ordre n'a pas d'importance et on peut n'en préciser que certains. Les valeurs par défaut garantissent un comportement fonctionnel même sans options.

Effets et nettoyage dans un composable

Un composable peut brancher des effets. On pense aux écouteurs globaux, aux minuteries, aux WebSockets. Le nettoyage se fait lors de la suppression du composant qui l'utilise. On place la logique dans onMounted() et onUnmounted() appelés depuis le composable. Cela fonctionne car la fonction s'exécute pendant l'étape d'initialisation du composant.

Exemple : Touches du clavier

Composable useClavier (src/composables/useClavier.js) :


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

            // Ce composable observe les touches du clavier pressées en temps réel.
            // Il expose une structure réactive contenant les touches actuellement actives.
            export function useClavier()
            {
                // Ensemble réactif qui stocke les touches enfoncées.
                // "Set" évite les doublons (une touche ne peut être listée qu'une seule fois).
                const touchesActives = ref(new Set());

                // Fonctions déclenchées lors de l'appui et du relâchement d'une touche.
                const presser = (e) => { touchesActives.value.add(e.key) };
                const relacher = (e) => { touchesActives.value.delete(e.key) };

                // Lorsque le composant qui utilise ce composable est monté,
                // on branche les écouteurs d'événements globaux sur "window".
                onMounted(() =>
                {
                    window.addEventListener('keydown', presser);
                    window.addEventListener('keyup', relacher);
                });

                // Quand le composant est démonté, on nettoie les écouteurs.
                // Cela évite les fuites mémoire et les comportements indésirables.
                onUnmounted(() =>
                {
                    window.removeEventListener('keydown', presser);
                    window.removeEventListener('keyup', relacher);
                });

                // On expose uniquement la valeur réactive.
                // Les composants peuvent l'observer et réagir aux changements.
                return { touchesActives };
            }
            

Utilisation dans le composant DemoClavier (src/components/DemoClavier.vue) :


                <script setup>
                import { computed } from 'vue';
                import { useClavier } from '../composables/useClavier.js';

                // On récupère la valeur réactive "touchesActives" depuis le composable.
                const { touchesActives } = useClavier();

                // Propriété calculée qui transforme l'ensemble des touches en texte lisible.
                // [...touchesActives.value] : convertit le Set en tableau.
                // .join(', ') : convertit le tableau en chaîne de caractères.
                // Si aucune touche n'est pressée, affiche "aucune".
                const affichage = computed(() => [...touchesActives.value].join(', ') || 'aucune');
                </script>

                <template>
                    <p>Touches pressées {{ affichage }}</p>
                </template>
            

Composant racine App.vue (src/App.vue) :


                <script setup>
                import { ref } from 'vue';
                import DemoClavier from './components/DemoClavier.vue';

                // Etat réactif qui contrôle la présence du composant DemoClavier.
                const afficherClavier = ref(true);

                const basculerAffichage = () =>
                {
                    afficherClavier.value = !afficherClavier.value;
                };
                </script>

                <template>
                    <main>
                        <h2>Démonstration des effets et du nettoyage dans un composable</h2>

                        <button @click="basculerAffichage">
                            {{ afficherClavier ? 'Masquer le composant clavier' : 'Afficher le composant clavier' }}
                        </button>

                        <hr />

                        <!-- Le composant est monté uniquement si "afficherClavier" vaut true. -->
                        <DemoClavier v-if="afficherClavier" />
                    </main>
                </template>
            

Lorsque le bouton est utilisé pour cacher le composant DemoClavier, celui-ci est retiré du DOM et détruit par Vue. Le hook onUnmounted() du composable useClavier() s'exécute alors, ce qui supprime les écouteurs globaux sur window. Quand on réaffiche le composant, il est monté à nouveau et onMounted() rebranche les écouteurs.

De l'état local à l'état partagé

Lorsqu'un composable est appelé dans un composant, il crée normalement son propre jeu de données réactives. Chaque instance possède donc un état local indépendant des autres. C'est ce comportement qu'on recherche la plupart du temps (chaque compteur, chaque formulaire ou chaque carte vit sa propre vie).

Exemple : État partagé minimal

L'exemple suivant illustre un cas typique d'état partagé à travers un panier d'achat. Deux composants distincts vont afficher et modifier la même liste d'articles. Lorsqu'un produit est ajouté dans l'un, il apparaît aussitôt dans l'autre, car tous deux accèdent à une seule et même source de données réactives. L'objectif est de montrer comment un composable peut servir de point commun à plusieurs composants pour synchroniser leurs informations.

Composable usePanier (src/composables/usePanier.js) :


            import { ref } from 'vue';

            // "panier" est défini en dehors de la fonction exportée.
            // Tous les composants partageront donc la même référence réactive.
            const panier = ref([]);

            export function usePanier()
            {
                // Ajoute un article au panier partagé.
                const ajouter = (article) => 
                {
                    panier.value.push(article);
                };

                // Vide complètement le panier.
                const vider = () => 
                {
                    panier.value = [];
                };

                // Chaque appel renvoie les mêmes données réactives.
                return { panier, ajouter, vider };
            }
            

Tous les composants qui appellent usePanier() partagent désormais la même référence réactive panier. Lorsqu'un produit est ajouté dans un composant, la mise à jour se reflète instantanément dans les autres. Ce comportement illustre un état partagé accessible à plusieurs composants en même temps. À l'inverse, un état local permettrait à chaque composant de gérer son propre panier de manière indépendante.

Composant enfant DemoPanier (src/components/DemoPanier.vue) :


                <script setup>
                import { usePanier } from '../composables/usePanier.js';

                // Chaque composant importe le même composable.
                // Comme la donnée réactive "panier" est défini au niveau module, ils partagent le même état.
                const { panier, ajouter } = usePanier();

                const ajouterArticle = () => 
                {
                    const id = panier.value.length + 1;
                    ajouter({ id, nom: 'Article : ' + id });
                };
                </script>

                <template>
                    <div class="bloc-panier">
                        <h3>Panier</h3>
                        <button @click="ajouterArticle">Ajouter article</button>
                        <ul>
                            <li v-for="article in panier" :key="article.id">
                                {{ article.nom }}
                            </li>
                        </ul>
                    </div>
                </template>
            

Composant racine App.vue (src/App.vue) :


                <script setup>
                import DemoPanier from './components/DemoPanier.vue';
                </script>

                <template>
                    <main>
                        <h2>Démonstration d'un état partagé</h2>
                        <DemoPanier />
                        <hr>
                        <DemoPanier />
                    </main>
                </template>
            

Les deux instances du composant DemoPanier utilisent le même composable usePanier(). Comme la variable panier est déclarée en dehors de la fonction du composable, elle est partagée entre toutes les utilisations. Lorsqu'un article est ajouté dans la première instance, la mise à jour apparaît aussitôt dans la seconde. L'exemple montre ainsi qu'un même état réactif peut être utilisé simultanément par plusieurs composants.

On peut facilement s'en assurer en déplaçant la déclaration de panier à l'intérieur de la fonction du composable. Chaque appel créerait alors sa propre donnée réactive, et les deux instances du composant auraient chacune un panier indépendant.

Exercices : Structurer la todoliste avec des composables

Exo-composables-01 : Centraliser la persistance dans un composable

Cet exercice vous propose de déplacer toute la logique de persistance de la todoliste dans un composable dédié. L'objectif est de rendre App.vue plus lisible en y gardant uniquement les données métier et les actions, tandis que le composable se charge de la communication avec le localStorage.

Objectif Déplacer la logique de lecture et d'écriture dans le localStorage dans un composable réutilisable useTodolistLocalStorage(). App.vue conserve les données métier (taches, prochainId, tri, etc.) tandis que le composable se concentre sur : la lecture initiale depuis le localStorage et l'enregistrement automatique des changements.

Pré-requis

Étape 1 : Créer le dossier et le fichier du composable

Objectif Préparer la structure de base du composable useTodolistLocalStorage() dans un dossier dédié.

Instructions

  1. Dans le dossier src, créez un nouveau dossier composables si ce n'est pas déjà fait.
  2. À l'intérieur, créez un fichier src/composables/useTodolistLocalStorage.js.
  3. Ajoutez une structure de base :
    
                            import { watch, onMounted } from 'vue';
    
                            export function useTodolistLocalStorage(taches, prochainId, options = {})
                            {
                                // Déstructurer les options pour récupérer les clés de stockage
                                const {
                                    cleTaches,
                                    cleProchainId
                                } = options;
    
                                // TODO :
                                // - fonction d'initialisation depuis localStorage
                                // - watchers pour enregistrer les changements
                            }
                        

Étape 2 : Déplacer l'initialisation dans le composable

Objectif Déplacer la lecture initiale du localStorage depuis App.vue vers le composable.

Instructions

  1. Retournez dans App.vue et repérez le code qui lit les données dans le localStorage au démarrage (lecture de CLE_LOCALSTORAGE_TACHES et de CLE_LOCALSTORAGE_PROCHAIN_ID, utilisation de JSON.parse(), etc.).
  2. Copiez ce code dans une nouvelle fonction définie à l'intérieur du composable useTodolistLocalStorage() :
    
                            function initialiserDepuisLocalStorage()
                            {
                                // 1. Lire la valeur de cleTaches dans localStorage
                                // 2. Si elle existe, la convertir en tableau d'objets
                                //    et remplir la donnée réactive "taches"
                                // 3. Lire la valeur de cleProchainId
                                // 4. Si elle existe, l'utiliser pour initialiser "prochainId"
                                // 5. Sinon, conserver les valeurs par défaut déjà présentes
                            }
                        
    Adaptez cette fonction avec votre propre logique. Utilisez cleTaches et cleProchainId au lieu des constantes globales.
  3. Toujours dans useTodolistLocalStorage(), appelez cette fonction dans un hook onMounted() :
    
                            onMounted(() =>
                            {
                                initialiserDepuisLocalStorage();
                            });
                        
    L'initialisation est désormais déclenchée automatiquement lorsque le composant qui utilise le composable est monté.

Étape 3 : Déplacer les watchers dans le composable

Objectif Centraliser la sauvegarde dans le localStorage directement dans le composable.

Instructions

  1. Dans App.vue, repérez les appels à watch() qui sauvegardent taches et prochainId dans le localStorage. Par exemple :
    • un watcher sur la liste des tâches
    • un watcher sur prochainId
  2. Coupez ces watchers et collez-les dans le fichier useTodolistLocalStorage.js, à l'intérieur de la fonction useTodolistLocalStorage().
  3. Adaptez les clés utilisées dans localStorage.setItem() pour employer cleTaches et cleProchainId. Par exemple :
    
                            watch(
                                taches,
                                (nouvellesTaches) =>
                                {
                                    localStorage.setItem(
                                        cleTaches,
                                        JSON.stringify(nouvellesTaches)
                                    );
                                },
                                {
                                    // Gardez ou non "deep:true" selon la manière
                                    // dont vous avez créé "taches" (ref([]) ou reactive([]))
                                }
                            );
    
                            watch(
                                prochainId,
                                (nouvelleValeur) =>
                                {
                                    localStorage.setItem(
                                        cleProchainId,
                                        String(nouvelleValeur)
                                    );
                                }
                            );
                        
    Adaptez la signature des callbacks à votre propre code.
  4. Supprimez les anciens watchers de App.vue pour éviter la duplication. La persistance est maintenant entièrement gérée par le composable.

Étape 4 : Utiliser le composable dans App.vue

Objectif Brancher le composable autour des données réactives de App.vue sans changer le comportement de la todoliste.

Instructions

  1. Dans App.vue, importez le composable :
    
                            import { useTodolistLocalStorage } from './composables/useTodolistLocalStorage.js';
                        
  2. Après la création des données réactives (taches, prochainId) et la déclaration des constantes CLE_LOCALSTORAGE_TACHES et CLE_LOCALSTORAGE_PROCHAIN_ID, appelez le composable :
    
                            useTodolistLocalStorage(
                                taches,
                                prochainId,
                                {
                                    cleTaches: CLE_LOCALSTORAGE_TACHES,
                                    cleProchainId: CLE_LOCALSTORAGE_PROCHAIN_ID
                                }
                            );
                        
    Le composable ne retourne rien. Son rôle est de brancher les effets de persistance autour des données existantes.
  3. Vérifiez que le comportement global est identique :
    • ajout, suppression, tri, bascule terminé fonctionnent comme avant
    • actualiser la page restaure toujours la liste, l'ordre personnalisé et prochainId
    Si tout est correct, la persistance de la todoliste est désormais regroupée dans un composable réutilisable.

Exo-composables-02 : Extraire les valeurs dérivées dans des composables

Dans cet exercice, vous allez regrouper les propriétés calculées liées à la todoliste dans des composables dédiés. L'idée est d'alléger App.vue en déplaçant la logique dérivée tout en conservant les données au même endroit.

Objectif Déplacer les propriétés calculées existantes (compteurs, états dérivés, tri) dans un ou plusieurs composables pour clarifier le rôle de App.vue.

Pré-requis

Étape 1 : Composable pour les compteurs de tâches

Objectif Regrouper les computed liées aux compteurs et à la présence de tâches dans un composable useTodolistStats().

Instructions

  1. Créez un nouveau fichier src/composables/useTodolistStats.js.
  2. Ajoutez une structure de base :
    
                            import { computed } from 'vue';
    
                            export function useTodolistStats(taches)
                            {
                                // TODO :
                                // - nombreTotalTaches
                                // - nombreTachesTerminees
                                // - aDesTaches
    
                                return {
                                    // propriétés calculées à retourner
                                };
                            }
                        
  3. Reprenez dans App.vue les computed suivantes :
    • nombreTotalTaches
    • nombreTachesTerminees
    • aDesTaches
    Coupez-les et collez-les dans la fonction useTodolistStats().
  4. Adaptez leur définition pour qu'elles se basent sur le paramètre taches reçu par le composable. Selon votre code, vous utilisiez soit :
    • un tableau réactif avec reactive([]) et la syntaxe taches.length,
    • une ref avec ref([]) et la syntaxe taches.value.length.
    Gardez la même approche dans le composable.
  5. Terminez la fonction en retournant un objet contenant ces propriétés calculées :
    
                            export function useTodolistStats(taches)
                            {
                                const nombreTotalTaches = computed(() =>
                                {
                                    // Retourner le nombre de tâches
                                });
    
                                const nombreTachesTerminees = computed(() =>
                                {
                                    // Retourner le nombre de tâches dont "terminee" vaut true
                                });
    
                                const aDesTaches = computed(() =>
                                {
                                    // Retourner true si la liste contient au moins une tâche
                                });
    
                                return {
                                    nombreTotalTaches,
                                    nombreTachesTerminees,
                                    aDesTaches
                                };
                            }
                        
  6. Dans App.vue, importez le composable et utilisez-le après la création de la donnée réactive taches :
    
                            import { useTodolistStats } from './composables/useTodolistStats.js';
    
                            const { nombreTotalTaches, nombreTachesTerminees, aDesTaches } = useTodolistStats(taches);
                        
    Ces computed remplacent celles qui étaient déclarées directement dans App.vue.
  7. Vérifiez que le template s'affiche comme avant. Les compteurs et le rendu conditionnel basé sur aDesTaches doivent continuer à fonctionner sans modification visuelle.

Étape 2 : Composable pour le tri et l'état de tri

Objectif Déplacer la logique de tri et l'état associé dans un composable useTodolistTri() pour isoler toute la logique de tri.

Instructions

  1. Créez un nouveau fichier src/composables/useTodolistTri.js.
  2. Ajoutez une structure de base :
    
                            import { computed } from 'vue';
    
                            export function useTodolistTri(taches, triCritere)
                            {
                                // TODO :
                                // - tachesTriees
                                // - peutUtiliserTriManuel
    
                                return {
                                    // propriétés calculées à retourner
                                };
                            }
                        
  3. Dans App.vue, repérez les computed suivantes :
    • tachesTriees
    • peutUtiliserTriManuel
    Coupez-les et collez-les dans le composable useTodolistTri().
  4. Adaptez la computed tachesTriees pour qu'elle utilise le paramètre triCritere reçu par le composable. Continuez à utiliser toSorted() afin de ne jamais modifier directement la liste taches.
  5. Adaptez la computed peutUtiliserTriManuel pour qu'elle se base sur le même paramètre triCritere :
    
                            const peutUtiliserTriManuel = computed(() =>
                            {
                                // Retourner true si triCritere vaut "manuel"
                                // et false dans les autres cas
                            });
                        
  6. Terminez le composable en retournant ces deux propriétés :
    
                            export function useTodolistTri(taches, triCritere)
                            {
                                const tachesTriees = computed(() =>
                                {
                                    // Retourner la version triée de "taches"
                                    // en fonction de "triCritere"
                                });
    
                                const peutUtiliserTriManuel = computed(() =>
                                {
                                    // Retourner true ou false selon la valeur de triCritere
                                });
    
                                return {
                                    tachesTriees,
                                    peutUtiliserTriManuel
                                };
                            }
                        
  7. Dans App.vue, importez ce composable et utilisez-le après la déclaration de taches et de triCritere :
    
                            import { useTodolistTri } from './composables/useTodolistTri.js';
    
                            const { tachesTriees, peutUtiliserTriManuel } = useTodolistTri(taches, triCritere);
                        
  8. Dans le template, vérifiez que :
    • la liste est toujours affichée en parcourant tachesTriees dans le v-for
    • les boutons de tri manuel utilisent toujours peutUtiliserTriManuel pour leur état désactivé
    Le comportement visuel doit rester identique.

Bilan

Votre todoliste est maintenant organisée de la manière suivante :

Cette séparation prépare la réutilisation de ces composables dans d'autres listes ou dans des projets plus grands, sans modifier la manière dont le template affiche la todoliste.