Contactez-nous

Fonction de nettoyage (Cleanup function) : Prévenir les fuites mémoire

Apprenez à utiliser la fonction de nettoyage (cleanup) de useEffect en React pour annuler abonnements, timers et requêtes, prévenant ainsi les fuites mémoire.

Pourquoi le nettoyage est-il indispensable ?

De nombreux effets de bord que nous mettons en place avec `useEffect` impliquent la création de ressources ou d'abonnements qui nécessitent d'être explicitement libérés ou annulés lorsque le composant n'est plus nécessaire. Pensez aux timers (`setTimeout`, `setInterval`), aux abonnements à des événements (`window.addEventListener`, WebSockets, API tierces), ou aux requêtes réseau en cours.

Si nous ne nettoyons pas ces ressources lorsque le composant est retiré du DOM (démonté) ou lorsque l'effet est sur le point de se ré-exécuter avec de nouvelles dépendances, nous risquons de graves problèmes :

  • Fuites de mémoire : Les abonnements ou timers non annulés peuvent conserver des références à des objets ou fonctions du composant démonté, empêchant le garbage collector de libérer la mémoire associée.
  • Exécutions inutiles ou erronées : Un timer pourrait se déclencher et tenter de mettre à jour l'état d'un composant qui n'existe plus, provoquant des erreurs. Plusieurs écouteurs d'événements identiques pourraient être ajoutés sans que les précédents soient retirés.
  • Consommation de ressources : Des requêtes réseau inutiles pourraient continuer en arrière-plan, consommant de la bande passante et des ressources serveur.

Pour gérer cela proprement, React permet à la fonction passée à `useEffect` de retourner une autre fonction : la fonction de nettoyage.

Syntaxe et fonctionnement de la fonction de nettoyage

Pour spécifier une logique de nettoyage pour un effet, il suffit de retourner une fonction depuis la fonction principale de `useEffect`. Cette fonction retournée sera automatiquement appelée par React au bon moment.

useEffect(() => {
  // --- Code de l'effet (Setup) ---
  console.log('Effet en cours...');
  const timerId = setTimeout(() => {
    console.log('Timer déclenché !');
  }, 3000);

  // --- Fonction de nettoyage retournée ---
  return () => {
    // Ce code est exécuté lors du nettoyage
    console.log('Nettoyage de l\'effet (clearTimeout)...');
    clearTimeout(timerId); // Annule le timer
  };
}, []); // Exemple avec dépendances vides pour exécution unique

React exécute la fonction de nettoyage dans deux situations principales :

  1. Avant le démontage du composant : Lorsque le composant est sur le point d'être retiré de l'interface utilisateur (par exemple, à cause d'un rendu conditionnel ou d'un changement de route), React exécute la fonction de nettoyage de tous ses effets pour libérer les ressources.
  2. Avant la prochaine exécution de l'effet : Si l'effet est ré-exécuté parce qu'une de ses dépendances a changé, React exécute d'abord la fonction de nettoyage de l'exécution *précédente* de l'effet. Cela garantit que l'ancien effet est bien nettoyé avant que le nouveau ne soit mis en place. Si l'effet s'exécute après chaque rendu (pas de tableau de dépendances), le nettoyage s'exécute avant chaque nouvelle exécution (sauf la première).

Ce mécanisme assure que pour chaque mise en place d'un effet (setup), il y a une opération de nettoyage correspondante (cleanup), évitant l'accumulation de ressources ou d'abonnements.

Exemples concrets de nettoyage

Nettoyage d'un écouteur d'événement :
function WindowWidthLogger() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => {
      console.log('Fenêtre redimensionnée, nouvelle largeur :', window.innerWidth);
      setWidth(window.innerWidth);
    };

    console.log('Ajout de l\'écouteur resize');
    window.addEventListener('resize', handleResize);

    // Fonction de nettoyage
    return () => {
      console.log('Retrait de l\'écouteur resize');
      window.removeEventListener('resize', handleResize);
    };
  }, []); // Exécution unique au montage

  return 
Largeur actuelle : {width}px
; }
Nettoyage d'un `setInterval` :
function Horloge() {
  const [time, setTime] = useState(new Date());

  useEffect(() => {
    console.log('Démarrage de l intervalle');
    const intervalId = setInterval(() => {
      setTime(new Date());
    }, 1000);

    // Nettoyage : arrête l'intervalle
    return () => {
      console.log('Arrêt de l intervalle');
      clearInterval(intervalId);
    };
  }, []);

  return 
Heure : {time.toLocaleTimeString()}
; }
Annulation d'une requête `fetch` (avec AbortController) :

C'est un cas d'usage important pour éviter de traiter la réponse d'une requête si le composant est démonté avant qu'elle n'arrive, ou si une nouvelle requête est lancée (basée sur un changement de dépendance) avant la fin de la précédente.

function UserData({ userId }) {
  const [user, setUser] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    // Crée un AbortController pour cette exécution de l'effet
    const controller = new AbortController();
    const signal = controller.signal;

    console.log(`Fetching data pour ${userId}`);
    fetch(`/api/users/${userId}`, { signal }) // Passe le signal à fetch
      .then(response => {
        if (!response.ok) {
          throw new Error('Erreur réseau');
        }
        return response.json();
      })
      .then(data => setUser(data))
      .catch(err => {
        // Ignore l'erreur si elle est due à l'annulation (AbortError)
        if (err.name !== 'AbortError') {
          console.error("Erreur fetch:", err);
          setError(err);
        }
      });

    // Fonction de nettoyage : annule la requête si elle est toujours en cours
    return () => {
      console.log(`Nettoyage : Annulation de la requête pour ${userId}`);
      controller.abort();
    };
  }, [userId]); // Refait la requête (et annule la précédente) si userId change

  // ... Reste du composant pour afficher user ou error
}

Conclusion : Une pratique essentielle pour la robustesse

Implémenter correctement la fonction de nettoyage dans `useEffect` n'est pas une option, mais une nécessité pour la plupart des effets de bord qui établissent des connexions, des abonnements ou des timers. Elle est la garantie que votre composant se comporte de manière prévisible, ne laisse pas de ressources actives après son départ, et ne cause pas de fuites de mémoire.

Lorsque vous écrivez un `useEffect`, posez-vous toujours la question : "Est-ce que cet effet nécessite un nettoyage ?". Si la réponse est oui (ce qui est souvent le cas pour les interactions avec des API de navigateur ou des systèmes externes), assurez-vous de retourner une fonction qui effectue l'opération inverse ou l'annulation nécessaire. C'est une étape cruciale pour construire des applications React fiables et performantes.