OUJOOD.COM
Introduction
Le Hook `useCallback` est utilisé pour mémoriser des fonctions afin d'éviter leur recréation lors de chaque re-render d'un composant. Cela permet d'améliorer les performances de vos applications React, surtout lorsque ces fonctions sont passées comme props à des enfants optimisés avec `React.memo`.
Dans ce chapitre, nous allons explorer :
- Qu'est-ce que `useCallback` ?
- Comment créer et utiliser une fonction mémorisée.
- Des exemples pratiques pour illustrer son fonctionnement.
1. Qu'est-ce que `useCallback` ?
`useCallback` est un Hook React qui renvoie une version mémorisée d'une fonction. Cette fonction ne sera recréée que si l'une de ses dépendances change. Voici pourquoi cela est important :
- Optimisation des Performances : En mémorisant des fonctions, vous évitez des re-renders inutiles chez les enfants optimisés avec `React.memo`.
- Réutilisabilité : Les fonctions mémorisées peuvent être réutilisées sans avoir besoin d'être recalculées à chaque re-render.
2. Création d'une Fonction Mémorisée avec `useCallback`
Voici un exemple où nous utilisons `useCallback` avec `React.memo` pour éviter des re-renders inutiles du composant enfant. Notez que nous devons soigneusement choisir les dépendances de `useCallback` pour garantir que la fonction ne soit pas recréée lors de chaque re-render.
import React, { useState, useCallback } from 'react'; import { createRoot } from 'react-dom/client'; // Composant enfant optimisé avec React.memo const BoutonMemo = React.memo(({ onClick }) => { console.log('BoutonMemo rendu'); return <button onClick={onClick}>Cliquez ici</button>; }); function App() { const [compte, setCompte] = useState(0); // État principal // Fonction mémorisée avec useCallback const handleClick = useCallback(() => { setCompte((prevState) => prevState + 1); // Utilise une fonction pour mettre à jour l'état }, []); // Aucune dépendance : la fonction reste inchangée tant que le composant parent existe return ( <div> <h1>Compteur : {compte}</h1> <BoutonMemo onClick={handleClick} /> {/* Passage de la fonction mémorisée */} </div> ); } const root = createRoot(document.getElementById('root')); root.render(<App />);
Explication détaillée ligne par ligne :
-
import React, { useState, useCallback } from 'react';
:- Nous importons React ainsi que les Hooks
useState
etuseCallback
. useState
gère l'état local du composant parent (compte
).useCallback
est utilisé pour mémoriser une fonction et éviter sa recréation lors de chaque re-render.
- Nous importons React ainsi que les Hooks
-
const BoutonMemo = React.memo(({ onClick }) => { ... });
:- Ce composant enfant est enveloppé par
React.memo
, ce qui signifie qu'il ne se re-renderera que si ses props changent. console.log('BoutonMemo rendu');
: Affiche un message dans la console chaque fois que le composant est rendu, facilitant ainsi le débogage.
- Ce composant enfant est enveloppé par
-
const handleClick = useCallback(() => { ... }, []);
:- Cette ligne utilise
useCallback
pour mémoriser la fonctionhandleClick
. - Dépendances vides ([]) : La fonction
handleClick
ne sera jamais recréée, car elle ne dépend d'aucune variable externe. - Cela permet à
BoutonMemo
d'éviter des re-renders inutiles, même lorsque l'étatcompte
change.
- Cette ligne utilise
-
setCompte((prevState) => prevState + 1);
:- Cette ligne met à jour l'état
compte
de manière sécurisée en utilisant une fonction de rappel. - En passant une fonction plutôt qu'une valeur directe, nous assurons que la mise à jour de l'état se base toujours sur l'état précédent, évitant ainsi des problèmes potentiels liés aux re-renders asynchrones.
- Cette ligne met à jour l'état
-
<BoutonMemo onClick={handleClick} />
:- Le composant enfant
BoutonMemo
reçoit la fonction mémoriséehandleClick
via la proponClick
. - Grâce à
useCallback
, cette fonction reste inchangée tant que le composant parent existe, ce qui permet àReact.memo
de fonctionner correctement.
- Le composant enfant
-
const root = createRoot(document.getElementById('root'));
:- Cette ligne crée une instance de racine React appelée
root
. document.getElementById('root')
spécifie l'élément HTML où le composant sera inséré.
- Cette ligne crée une instance de racine React appelée
-
root.render(<App />);
:- Rend le composant
App
dans l'élément HTML ayant l'idroot
.
- Rend le composant
Pourquoi Ne Pas Inclure [compte]
comme Dépendance ?
Si nous avions inclus [compte]
comme dépendance dans useCallback
, cela aurait entraîné une recréation de la fonction handleClick
à chaque changement de l'état compte
. Voici pourquoi cela pose problème :
- Recréation de la Fonction : Lorsque
compte
change, React considère quehandleClick
est une nouvelle fonction, même si son comportement reste identique. - Re-render Inutile avec
React.memo
: Le composant enfantBoutonMemo
compare ses props actuelles avec les précédentes. SihandleClick
change (même si cela n'est pas nécessaire),BoutonMemo
se re-renderera inutilement.
Pour éviter cela, nous avons supprimé [compte]
des dépendances de useCallback
. Cela garantit que handleClick
reste inchangée tant que le composant parent existe, maximisant ainsi les performances grâce à React.memo
.
Quand Inclure des Dépendances dans useCallback
?
Il est important d'inclure des dépendances dans useCallback
uniquement lorsque la fonction nécessite des variables externes pour son fonctionnement. Par exemple :
- Si la fonction utilise une variable d'état ou une prop : Ajoutez cette variable/prop comme dépendance.
- Si la fonction ne dépend d'aucune variable externe : Utilisez un tableau vide (
[]
) comme dépendance pour garantir que la fonction reste inchangée.
3. Importance de `useCallback` avec `React.memo`
Lorsque vous utilisez React.memo
pour optimiser un composant enfant, il est crucial de mémoriser les fonctions transmises via les props. Sinon, même si les props n'ont pas changé, le composant enfant pourrait se re-render inutilement à chaque re-render du parent.
Voici un exemple plus avancé où nous combinons useCallback
et React.memo
pour optimiser une liste de tâches :
import React, { useState, useCallback } from 'react'; import { createRoot } from 'react-dom/client'; // Composant enfant optimisé avec React.memo const TacheMemo = React.memo(({ tache, supprimerTache }) => { console.log('TacheMemo rendu'); return ( <li> {tache.texte} <button onClick={() => supprimerTache(tache.id)}>Supprimer</button> </li> ); }); function App() { const [taches, setTaches] = useState([ { id: 1, texte: 'Tâche 1' }, { id: 2, texte: 'Tâche 2' } ]); // Fonction mémorisée pour ajouter une nouvelle tâche const ajouterTache = useCallback((nouvelleTache) => { setTaches([...taches, { id: Date.now(), texte: nouvelleTache }]); }, [taches]); // Fonction mémorisée pour supprimer une tâche const supprimerTache = useCallback((id) => { setTaches(taches.filter((tache) => tache.id !== id)); }, [taches]); return ( <div> <h1>Gestion de Tâches avec useCallback</h1> <input type="text" placeholder="Ajoutez une tâche..." /> <button onClick={() => ajouterTache('Nouvelle tâche')}>Ajouter</button> <ul> {taches.map((tache) => ( <TacheMemo key={tache.id} tache={tache} supprimerTache={supprimerTache} /> ))} </ul> </div> ); } const root = createRoot(document.getElementById('root')); root.render(<App />);
Explication détaillée ligne par ligne :
-
const TacheMemo = React.memo(({ tache, supprimerTache }) => { ... });
:- Ce composant enfant est enveloppé par
React.memo
, ce qui signifie qu'il ne se re-renderera que si ses props changent. console.log('TacheMemo rendu');
: Affiche un message dans la console chaque fois que le composant est rendu, facilitant ainsi le débogage.
- Ce composant enfant est enveloppé par
-
const ajouterTache = useCallback((nouvelleTache) => { ... }, [taches]);
:- Cette ligne utilise
useCallback
pour mémoriser la fonctionajouterTache
. [taches]
spécifie les dépendances de la fonction. Elle ne sera recréée que sitaches
change.
- Cette ligne utilise
-
setTaches([...taches, { id: Date.now(), texte: nouvelleTache }]);
:- Cette ligne met à jour l'état
taches
en ajoutant une nouvelle tâche au tableau existant. Date.now()
génère une valeur unique pour l'ID de la nouvelle tâche.
- Cette ligne met à jour l'état
-
const supprimerTache = useCallback((id) => { ... }, [taches]);
:- Cette ligne utilise
useCallback
pour mémoriser la fonctionsupprimerTache
. [taches]
spécifie les dépendances de la fonction. Elle ne sera recréée que sitaches
change.
- Cette ligne utilise
-
setTaches(taches.filter((tache) => tache.id !== id));
:- Cette ligne filtre le tableau
taches
pour exclure la tâche correspondant à l'ID spécifié.
- Cette ligne filtre le tableau
-
<TacheMemo key={tache.id} tache={tache} supprimerTache={supprimerTache} />
:- Chaque élément de la liste est rendu sous forme de composant
TacheMemo
. - Les props
tache
etsupprimerTache
sont transmises au composant enfant.
- Chaque élément de la liste est rendu sous forme de composant
4. Avantages de `useCallback`
Utiliser `useCallback` offre plusieurs avantages significatifs :
- Performances Optimisées : En mémorisant des fonctions, vous évitez des re-renders inutiles chez les enfants optimisés avec
React.memo
. - Réutilisabilité : Les fonctions mémorisées peuvent être réutilisées sans avoir besoin d'être recalculées à chaque re-render.
- Code Plus Propre : Centralise la logique de mémorisation, rendant ainsi votre code plus maintenable et lisible.
Conclusion
Vous avez maintenant appris à utiliser le Hook `useCallback` pour mémoriser des fonctions et optimiser les performances de vos applications React. En combinant useCallback
avec React.memo
, vous pouvez rendre vos composants enfants beaucoup plus efficaces.
Prochain chapitre : useMemo