Un guide complet sur React useEffect, couvrant la gestion des effets de bord, les modèles de nettoyage et les meilleures pratiques pour créer des applications React performantes.
React useEffect : Maîtriser les effets de bord et les modèles de nettoyage
useEffect est un Hook React fondamental qui vous permet d'effectuer des effets de bord dans vos composants fonctionnels. Comprendre comment l'utiliser efficacement est crucial pour construire des applications React robustes et maintenables. Ce guide complet explore les subtilités de useEffect, couvrant divers scénarios d'effets de bord, des modèles de nettoyage et les meilleures pratiques.
Que sont les effets de bord ?
Dans le contexte de React, un effet de bord est toute opération qui interagit avec le monde extérieur ou modifie quelque chose en dehors de la portée du composant. Les exemples courants incluent :
- Récupération de données : Effectuer des appels API pour récupérer des données d'un serveur.
- Manipulation du DOM : Modifier directement le DOM (bien que React encourage les mises à jour déclaratives).
- Mise en place d'abonnements : S'abonner à des événements ou à des sources de données externes.
- Utilisation de minuteurs : Mettre en place
setTimeoutousetInterval. - Journalisation : Écrire dans la console ou envoyer des données à des services d'analyse.
- Interaction directe avec les API du navigateur : Comme accéder au
localStorageou utiliser l'API de géolocalisation.
Les composants React sont conçus pour être des fonctions pures, ce qui signifie qu'ils doivent toujours produire le même résultat pour une même entrée (props et état). Les effets de bord brisent cette pureté, car ils peuvent introduire un comportement imprévisible et rendre les composants plus difficiles à tester et à raisonner. useEffect fournit un moyen contrôlé de gérer ces effets de bord.
Comprendre le Hook useEffect
Le Hook useEffect accepte deux arguments :
- Une fonction contenant le code à exécuter en tant qu'effet de bord.
- Un tableau de dépendances optionnel.
Syntaxe de base :
useEffect(() => {
// Code de l'effet de bord ici
}, [/* Tableau de dépendances */]);
Le tableau de dépendances
Le tableau de dépendances est crucial pour contrôler quand la fonction d'effet est exécutée. C'est un tableau de valeurs (généralement des props ou des variables d'état) dont l'effet dépend. useEffect n'exécutera la fonction d'effet que si l'une des valeurs du tableau de dépendances a changé depuis le dernier rendu.
Scénarios courants du tableau de dépendances :
- Tableau de dépendances vide (
[]) : L'effet ne s'exécute qu'une seule fois, après le rendu initial. C'est souvent utilisé pour des tâches d'initialisation, comme la récupération de données au montage du composant. - Tableau de dépendances avec des valeurs (
[prop1, state1]) : L'effet s'exécute chaque fois que l'une des dépendances spécifiées change. C'est utile pour réagir aux changements de props ou d'état et mettre à jour le composant en conséquence. - Pas de tableau de dépendances (
undefined) : L'effet s'exécute après chaque rendu. C'est généralement déconseillé, car cela peut entraîner des problèmes de performance et des boucles infinies si ce n'est pas géré avec soin.
Modèles et exemples courants avec useEffect
1. Récupération de données
La récupération de données est l'un des cas d'utilisation les plus courants pour useEffect. Voici un exemple de récupération de données utilisateur depuis une API :
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
setLoading(true);
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`Erreur HTTP ! statut : ${response.status}`);
}
const data = await response.json();
setUser(data);
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
fetchData();
}, [userId]);
if (loading) return Chargement des données utilisateur...
;
if (error) return Erreur : {error.message}
;
if (!user) return Aucune donnée utilisateur disponible.
;
return (
{user.name}
Email : {user.email}
Lieu : {user.location}
);
}
export default UserProfile;
Explication :
- Le hook
useEffectest utilisé pour récupérer les données de l'utilisateur lorsque la propuserIdchange. - Le tableau de dépendances est
[userId], donc l'effet se réexécutera chaque fois que la propuserIdsera mise à jour. - La fonction
fetchDataest une fonctionasyncqui effectue un appel API en utilisantfetch. - La gestion des erreurs est incluse à l'aide d'un bloc
try...catch. - Les états de chargement et d'erreur sont utilisés pour afficher des messages appropriés à l'utilisateur.
2. Mise en place d'abonnements et d'écouteurs d'événements
useEffect est également utile pour mettre en place des abonnements à des sources de données externes ou des écouteurs d'événements. Il est crucial de nettoyer ces abonnements lorsque le composant est démonté pour éviter les fuites de mémoire.
import React, { useState, useEffect } from 'react';
function OnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
function handleStatusChange() {
setIsOnline(navigator.onLine);
}
window.addEventListener('online', handleStatusChange);
window.addEventListener('offline', handleStatusChange);
// Fonction de nettoyage
return () => {
window.removeEventListener('online', handleStatusChange);
window.removeEventListener('offline', handleStatusChange);
};
}, []);
return (
Vous êtes actuellement : {isOnline ? 'En ligne' : 'Hors ligne'}
);
}
export default OnlineStatus;
Explication :
- Le hook
useEffectmet en place des écouteurs d'événements pour les événementsonlineetoffline. - Le tableau de dépendances est
[], donc l'effet ne s'exécute qu'une seule fois au montage du composant. - La fonction de nettoyage (retournée par la fonction d'effet) supprime les écouteurs d'événements lorsque le composant est démonté.
3. Utilisation de minuteurs
Les minuteurs, tels que setTimeout et setInterval, peuvent également être gérés avec useEffect. Encore une fois, il est essentiel de nettoyer le minuteur lorsque le composant est démonté pour éviter les fuites de mémoire.
import React, { useState, useEffect } from 'react';
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setCount((prevCount) => prevCount + 1);
}, 1000);
// Fonction de nettoyage
return () => {
clearInterval(intervalId);
};
}, []);
return (
Temps écoulé : {count} secondes
);
}
export default Timer;
Explication :
- Le hook
useEffectmet en place un intervalle qui incrémente l'étatcountchaque seconde. - Le tableau de dépendances est
[], donc l'effet ne s'exécute qu'une seule fois au montage du composant. - La fonction de nettoyage (retournée par la fonction d'effet) nettoie l'intervalle lorsque le composant est démonté.
4. Manipulation directe du DOM
Bien que React encourage les mises à jour déclaratives, il peut y avoir des situations où vous devez manipuler directement le DOM. useEffect peut être utilisé à cette fin, mais cela doit être fait avec prudence. Envisagez d'abord des alternatives comme les refs.
import React, { useRef, useEffect } from 'react';
function FocusInput() {
const inputRef = useRef(null);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return (
);
}
export default FocusInput;
Explication :
- Le hook
useRefest utilisé pour créer une ref vers l'élément d'entrée. - Le hook
useEffectmet le focus sur l'élément d'entrée après le rendu initial. - Le tableau de dépendances est
[], donc l'effet ne s'exécute qu'une seule fois au montage du composant.
Fonctions de nettoyage : Prévenir les fuites de mémoire
L'un des aspects les plus importants de l'utilisation de useEffect est de comprendre la fonction de nettoyage. La fonction de nettoyage est une fonction retournée par la fonction d'effet. Elle est exécutée lorsque le composant est démonté, ou avant que la fonction d'effet ne s'exécute à nouveau (si les dépendances ont changé).
L'objectif principal de la fonction de nettoyage est de prévenir les fuites de mémoire. Les fuites de mémoire se produisent lorsque des ressources (telles que des écouteurs d'événements, des minuteurs ou des abonnements) ne sont pas correctement libérées lorsqu'elles ne sont plus nécessaires. Cela peut entraîner des problèmes de performance et, dans les cas graves, des plantages d'application.
Quand utiliser les fonctions de nettoyage
Vous devriez toujours utiliser une fonction de nettoyage lorsque votre fonction d'effet effectue l'une des actions suivantes :
- Met en place des abonnements à des sources de données externes.
- Ajoute des écouteurs d'événements à la fenêtre ou au document.
- Utilise des minuteurs (
setTimeoutousetInterval). - Modifie directement le DOM.
Comment fonctionnent les fonctions de nettoyage
La fonction de nettoyage est exécutée dans les scénarios suivants :
- Démontage du composant : Lorsque le composant est retiré du DOM.
- Ré-exécution de l'effet : Avant que la fonction d'effet ne soit ré-exécutée en raison de changements dans les dépendances. Cela garantit que l'effet précédent est correctement nettoyé avant l'exécution du nouvel effet.
Exemple de fonction de nettoyage (revisité)
Revenons à l'exemple OnlineStatus de tout à l'heure :
import React, { useState, useEffect } from 'react';
function OnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
function handleStatusChange() {
setIsOnline(navigator.onLine);
}
window.addEventListener('online', handleStatusChange);
window.addEventListener('offline', handleStatusChange);
// Fonction de nettoyage
return () => {
window.removeEventListener('online', handleStatusChange);
window.removeEventListener('offline', handleStatusChange);
};
}, []);
return (
Vous êtes actuellement : {isOnline ? 'En ligne' : 'Hors ligne'}
);
}
export default OnlineStatus;
Dans cet exemple, la fonction de nettoyage supprime les écouteurs d'événements qui ont été ajoutés dans la fonction d'effet. Cela prévient les fuites de mémoire en s'assurant que les écouteurs d'événements ne sont plus actifs lorsque le composant est démonté ou lorsque l'effet doit être ré-exécuté.
Meilleures pratiques pour l'utilisation de useEffect
Voici quelques meilleures pratiques à suivre lors de l'utilisation de useEffect :
- Gardez les effets ciblés : Chaque
useEffectdevrait être responsable d'un seul effet de bord bien défini. Évitez de combiner plusieurs effets de bord non liés dans un seuluseEffect. Cela rend votre code plus modulaire, testable et facile à comprendre. - Utilisez les tableaux de dépendances judicieusement : Réfléchissez attentivement aux dépendances de chaque
useEffect. Ajouter des dépendances inutiles peut entraîner une exécution de l'effet plus fréquente que nécessaire, causant des problèmes de performance. Omettre des dépendances nécessaires peut empêcher l'effet de s'exécuter quand il le devrait, conduisant à un comportement inattendu. - Nettoyez toujours : Si votre fonction d'effet met en place des ressources (comme des écouteurs d'événements, des minuteurs ou des abonnements), fournissez toujours une fonction de nettoyage pour libérer ces ressources lorsque le composant est démonté ou lorsque l'effet doit être ré-exécuté. Cela prévient les fuites de mémoire.
- Évitez les boucles infinies : Soyez prudent lors de la mise à jour de l'état dans un
useEffect. Si la mise à jour de l'état provoque une ré-exécution de l'effet, cela peut entraîner une boucle infinie. Pour éviter cela, assurez-vous que la mise à jour de l'état est conditionnelle ou que les dépendances sont correctement configurées. - Considérez useCallback pour les fonctions de dépendance : Si vous passez une fonction en tant que dépendance à
useEffect, envisagez d'utiliseruseCallbackpour mémoïser la fonction. Cela empêche la fonction d'être recréée à chaque rendu, ce qui peut entraîner une ré-exécution inutile de l'effet. - Extrayez la logique complexe : Si votre
useEffectcontient une logique complexe, envisagez de l'extraire dans une fonction distincte ou un Hook personnalisé. Cela rend votre code plus lisible et maintenable. - Testez vos effets : Écrivez des tests pour vous assurer que vos effets fonctionnent correctement et que les fonctions de nettoyage libèrent correctement les ressources.
Techniques avancées avec useEffect
1. Utiliser useRef pour persister des valeurs entre les rendus
Parfois, vous devez persister une valeur entre les rendus sans provoquer un nouveau rendu du composant. useRef peut être utilisé à cette fin. Par exemple, vous pouvez utiliser useRef pour stocker une valeur précédente d'une prop ou d'une variable d'état.
import React, { useState, useEffect, useRef } from 'react';
function PreviousValue({ value }) {
const previousValue = useRef(null);
useEffect(() => {
previousValue.current = value;
}, [value]);
return (
Valeur actuelle : {value}, Valeur précédente : {previousValue.current}
);
}
export default PreviousValue;
Explication :
- Le hook
useRefest utilisé pour créer une ref afin de stocker la valeur précédente de la propvalue. - Le hook
useEffectmet à jour la ref chaque fois que la propvaluechange. - Le composant ne se re-rend pas lorsque la ref est mise à jour, car les refs ne déclenchent pas de nouveaux rendus.
2. Debouncing et Throttling
Le debouncing et le throttling sont des techniques utilisées pour limiter la fréquence à laquelle une fonction est exécutée. Cela peut être utile pour améliorer les performances lors de la gestion d'événements qui se déclenchent fréquemment, comme les événements scroll ou resize. useEffect peut être utilisé en combinaison avec des hooks personnalisés pour implémenter le debouncing et le throttling dans les composants React.
3. Créer des Hooks personnalisés pour des effets réutilisables
Si vous vous retrouvez à utiliser la même logique useEffect dans plusieurs composants, envisagez de créer un Hook personnalisé pour encapsuler cette logique. Cela favorise la réutilisation du code et rend vos composants plus concis.
Par exemple, vous pourriez créer un Hook personnalisé pour récupérer des données depuis une API :
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
setLoading(true);
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Erreur HTTP ! statut : ${response.status}`);
}
const data = await response.json();
setData(data);
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
fetchData();
}, [url]);
return { data, loading, error };
}
export default useFetch;
Ensuite, vous pouvez utiliser ce Hook personnalisé dans vos composants :
import React from 'react';
import useFetch from './useFetch';
function UserProfile({ userId }) {
const { data: user, loading, error } = useFetch(`https://api.example.com/users/${userId}`);
if (loading) return Chargement des données utilisateur...
;
if (error) return Erreur : {error.message}
;
if (!user) return Aucune donnée utilisateur disponible.
;
return (
{user.name}
Email : {user.email}
Lieu : {user.location}
);
}
export default UserProfile;
Pièges courants à éviter
- Oublier les fonctions de nettoyage : C'est l'erreur la plus courante. Nettoyez toujours les ressources pour éviter les fuites de mémoire.
- Ré-exécutions inutiles : Assurez-vous que les tableaux de dépendances sont optimisés pour éviter les exécutions inutiles des effets.
- Boucles infinies accidentelles : Soyez extrêmement prudent avec les mises à jour d'état à l'intérieur de
useEffect. Vérifiez les conditions et les dépendances. - Ignorer les avertissements du linter : Les linters fournissent souvent des avertissements utiles sur les dépendances manquantes ou les problèmes potentiels avec l'utilisation de
useEffect. Prêtez attention à ces avertissements et corrigez-les.
Considérations globales pour useEffect
Lors du développement d'applications React pour un public mondial, tenez compte des points suivants lors de l'utilisation de useEffect pour la récupération de données ou les interactions avec des API externes :
- Points de terminaison d'API et localisation des données : Assurez-vous que vos points de terminaison d'API sont conçus pour gérer différentes langues et régions. Envisagez d'utiliser un réseau de diffusion de contenu (CDN) pour servir du contenu localisé.
- Formatage de la date et de l'heure : Utilisez des bibliothèques d'internationalisation (par exemple, l'API
Intlou des bibliothèques commemoment.js, mais considérez des alternatives commedate-fnspour des tailles de bundle plus petites) pour formater les dates et les heures en fonction de la locale de l'utilisateur. - Formatage des devises : De même, utilisez des bibliothèques d'internationalisation pour formater les devises en fonction de la locale de l'utilisateur.
- Formatage des nombres : Utilisez un formatage de nombre approprié pour les différentes régions (par exemple, les séparateurs décimaux, les séparateurs de milliers).
- Fuseaux horaires : Gérez correctement les conversions de fuseaux horaires lors de l'affichage des dates et des heures aux utilisateurs dans différents fuseaux horaires.
- Gestion des erreurs : Fournissez des messages d'erreur informatifs dans la langue de l'utilisateur.
Exemple de localisation de date :
import React, { useState, useEffect } from 'react';
function LocalizedDate() {
const [date, setDate] = useState(new Date());
useEffect(() => {
const timer = setInterval(() => {
setDate(new Date());
}, 1000);
return () => clearInterval(timer);
}, []);
const formattedDate = date.toLocaleDateString(undefined, {
year: 'numeric',
month: 'long',
day: 'numeric',
});
return Date actuelle : {formattedDate}
;
}
export default LocalizedDate;
Dans cet exemple, toLocaleDateString est utilisé pour formater la date en fonction de la locale de l'utilisateur. L'argument undefined indique à la fonction d'utiliser la locale par défaut du navigateur de l'utilisateur.
Conclusion
useEffect est un outil puissant pour gérer les effets de bord dans les composants fonctionnels de React. En comprenant les différents modèles et les meilleures pratiques, vous pouvez écrire des applications React plus performantes, maintenables et robustes. N'oubliez pas de toujours nettoyer vos effets, d'utiliser judicieusement les tableaux de dépendances et d'envisager de créer des Hooks personnalisés pour la logique réutilisable. En prêtant attention à ces détails, vous pouvez maîtriser useEffect et créer des expériences utilisateur incroyables pour un public mondial.