logo oujood
🔍

useCallback : Mémorisez des Fonctions pour Optimiser vos Performances

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.

📋 Copier le code

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 :

  1. import React, { useState, useCallback } from 'react'; :
    • Nous importons React ainsi que les Hooks useState et useCallback.
    • 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.
  2. 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.
  3. const handleClick = useCallback(() => { ... }, []); :
    • Cette ligne utilise useCallback pour mémoriser la fonction handleClick.
    • 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'état compte change.
  4. 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.
  5. <BoutonMemo onClick={handleClick} /> :
    • Le composant enfant BoutonMemo reçoit la fonction mémorisée handleClick via la prop onClick.
    • Grâce à useCallback, cette fonction reste inchangée tant que le composant parent existe, ce qui permet à React.memo de fonctionner correctement.
  6. 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é.
  7. root.render(<App />); :
    • Rend le composant App dans l'élément HTML ayant l'id root.

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 que handleClick est une nouvelle fonction, même si son comportement reste identique.
  • Re-render Inutile avec React.memo : Le composant enfant BoutonMemo compare ses props actuelles avec les précédentes. Si handleClick 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 :

📋 Copier le code

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 :

  1. 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.
  2. const ajouterTache = useCallback((nouvelleTache) => { ... }, [taches]); :
    • Cette ligne utilise useCallback pour mémoriser la fonction ajouterTache.
    • [taches] spécifie les dépendances de la fonction. Elle ne sera recréée que si taches change.
  3. 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.
  4. const supprimerTache = useCallback((id) => { ... }, [taches]); :
    • Cette ligne utilise useCallback pour mémoriser la fonction supprimerTache.
    • [taches] spécifie les dépendances de la fonction. Elle ne sera recréée que si taches change.
  5. setTaches(taches.filter((tache) => tache.id !== id)); :
    • Cette ligne filtre le tableau taches pour exclure la tâche correspondant à l'ID spécifié.
  6. <TacheMemo key={tache.id} tache={tache} supprimerTache={supprimerTache} /> :
    • Chaque élément de la liste est rendu sous forme de composant TacheMemo.
    • Les props tache et supprimerTache sont transmises au composant enfant.

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