Vous êtes sur la page 1sur 12

Utilisation du crochet

d'effet
Les crochets sont un nouvel ajout dans React 16.8. Ils vous
permettent d'utiliser l'état et d'autres fonctionnalités de React
sans écrire de classe.
Le crochet d'effet vous permet d'effectuer des effets secondaires dans les composants
fonctionnels :
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);

// Similar to componentDidMount and componentDidUpdate:


useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`; });
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

Cet extrait est basé sur le contre-exemple de la page précédente , mais nous y avons
ajouté une nouvelle fonctionnalité : nous avons défini le titre du document sur un message
personnalisé incluant le nombre de clics.
La récupération de données, la configuration d'un abonnement et la modification manuelle
du DOM dans les composants React sont tous des exemples d'effets secondaires. Que vous
ayez ou non l'habitude d'appeler ces opérations « effets secondaires » (ou simplement
« effets »), vous les avez probablement déjà effectuées dans vos composants auparavant.
Pointe
Si vous connaissez les méthodes de cycle de vie de la classe React, vous pouvez
considérer useEffectHook
comme componentDidMount, componentDidUpdateet componentWillUnmountcombinés.
Il existe deux types d'effets secondaires courants dans les composants React : ceux qui ne
nécessitent pas de nettoyage et ceux qui le nécessitent. Examinons cette distinction plus en
détail.
Effets sans nettoyage
Parfois, nous souhaitons exécuter du code supplémentaire après que React a mis à jour
le DOM. Les requêtes réseau, les mutations manuelles du DOM et la journalisation sont
des exemples courants d'effets qui ne nécessitent pas de nettoyage. Nous disons cela
parce que nous pouvons les exécuter et les oublier immédiatement. Comparons comment
les classes et les crochets nous permettent d'exprimer ces effets secondaires.
Exemple d'utilisation de classes
Dans les composants de classe React, la renderméthode elle-même ne devrait pas
provoquer d'effets secondaires. Ce serait trop tôt - nous voulons généralement effectuer
nos effets après que React a mis à jour le DOM.
C'est pourquoi dans les cours React, nous mettons des effets secondaires
dans componentDidMountet componentDidUpdate. Pour en revenir à notre exemple, voici un
composant de classe compteur React qui met à jour le titre du document juste après que
React ait apporté des modifications au DOM :
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}

componentDidMount() { document.title = `You clicked ${this.state.count} times`; }


componentDidUpdate() { document.title = `You clicked ${this.state.count} times`; }
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}

Notez comment nous devons dupliquer le code entre ces deux méthodes de cycle de
vie en classe.
En effet, dans de nombreux cas, nous souhaitons effectuer le même effet secondaire, que
le composant vienne d'être monté ou qu'il ait été mis à jour. Conceptuellement, nous
voulons que cela se produise après chaque rendu, mais les composants de la classe React
n'ont pas de méthode comme celle-ci. Nous pourrions extraire une méthode distincte,
mais nous aurions encore à l'appeler à deux endroits.
Voyons maintenant comment nous pouvons faire la même chose avec le useEffectHook.
Exemple d'utilisation de crochets
Nous avons déjà vu cet exemple en haut de cette page, mais regardons-le de plus près :
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);

useEffect(() => { document.title = `You clicked ${count} times`; });


return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

Que fait useEffect-il ? En utilisant ce Hook, vous dites à React que votre composant doit
faire quelque chose après le rendu. React se souviendra de la fonction que vous avez
passée (nous l'appellerons notre "effet") et l'appellera plus tard après avoir effectué les
mises à jour du DOM. À cet effet, nous définissons le titre du document, mais nous
pourrions également effectuer une récupération de données ou appeler une autre API
impérative.
Pourquoi est- useEffectil appelé à l'intérieur d'un composant ? Placer useEffectà
l'intérieur du composant nous permet d'accéder à la countvariable d'état (ou à tout
accessoire) directement depuis l'effet. Nous n'avons pas besoin d'une API spéciale pour le
lire - c'est déjà dans la portée de la fonction. Les crochets englobent les fermetures
JavaScript et évitent d'introduire des API spécifiques à React là où JavaScript fournit déjà
une solution.
S'exécute-t-il useEffectaprès chaque rendu ? Oui! Par défaut, il s'exécute à la fois après
le premier rendu et après chaque mise à jour. (Nous parlerons plus tard de la façon de
personnaliser cela .) Au lieu de penser en termes de "montage" et de "mise à jour", vous
trouverez peut-être plus facile de penser que les effets se produisent "après le
rendu". React garantit que le DOM a été mis à jour au moment où il exécute les effets.
Explication détaillée
Maintenant que nous en savons plus sur les effets, ces lignes devraient avoir un sens :
function Example() {
const [count, setCount] = useState(0);

useEffect(() => {
document.title = `You clicked ${count} times`;
});
}
Nous déclarons la countvariable d'état, puis nous disons à React que nous devons utiliser un
effet. Nous passons une fonction au useEffectHook. Cette fonction que nous
passons est notre effet. Dans notre effet, nous définissons le titre du document à l'aide de
l' document.titleAPI du navigateur. Nous pouvons lire la dernière countà l'intérieur de l'effet
car c'est dans le cadre de notre fonction. Lorsque React rend notre composant, il se
souviendra de l'effet que nous avons utilisé, puis exécutera notre effet après la mise à jour
du DOM. Cela se produit pour chaque rendu, y compris le premier.
Les développeurs JavaScript expérimentés peuvent remarquer que la fonction transmise
à useEffectsera différente sur chaque rendu. C'est intentionnel. En fait, c'est ce qui nous
permet de lire la countvaleur depuis l'intérieur de l'effet sans craindre qu'il ne devienne
obsolète. Chaque fois que nous effectuons un nouveau rendu, nous programmons un
effet différent , remplaçant le précédent. D'une certaine manière, cela fait que les effets se
comportent davantage comme une partie du résultat du rendu - chaque effet "appartient"
à un rendu particulier. Nous verrons plus clairement pourquoi cela est utile plus loin sur
cette page .
Pointe
Contrairement à componentDidMountou componentDidUpdate, les effets planifiés
avec useEffectn'empêchent pas le navigateur de mettre à jour l'écran. Cela rend votre
application plus réactive. La majorité des effets n'ont pas besoin de se produire de manière
synchrone. Dans les cas rares où ils le font (comme la mesure de la mise en page), il existe
un useLayoutEffectcrochet séparé avec une API identique à useEffect.

Effets avec nettoyage


Plus tôt, nous avons examiné comment exprimer les effets secondaires qui ne nécessitent
aucun nettoyage. Cependant, certains effets le font. Par exemple, nous pourrions
souhaiter configurer un abonnement à une source de données externe. Dans ce cas, il
est important de nettoyer afin de ne pas introduire de fuite mémoire ! Comparons
comment nous pouvons le faire avec les classes et avec les crochets.
Exemple d'utilisation de classes
Dans une classe React, vous configurez généralement un abonnement
dans componentDidMountet le nettoyez dans componentWillUnmount. Par exemple, disons que nous
avons un ChatAPImodule qui nous permet de nous abonner au statut en ligne d'un
ami. Voici comment nous pourrions nous abonner et afficher ce statut à l'aide d'une
classe :
class FriendStatus extends React.Component {
constructor(props) {
super(props);
this.state = { isOnline: null };
this.handleStatusChange = this.handleStatusChange.bind(this);
}

componentDidMount() { ChatAPI.subscribeToFriendStatus( this.props.friend.id,


this.handleStatusChange ); } componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id,
this.handleStatusChange ); } handleStatusChange(status) { this.setState({
isOnline: status.isOnline }); }
render() {
if (this.state.isOnline === null) {
return 'Loading...';
}
return this.state.isOnline ? 'Online' : 'Offline';
}
}

Remarquez comment componentDidMountet componentWillUnmountdoivent se refléter. Les


méthodes de cycle de vie nous obligent à diviser cette logique même si, conceptuellement,
le code dans les deux est lié au même effet.
Noter
Les lecteurs aux yeux d'aigle peuvent remarquer que cet exemple a également besoin
d'une componentDidUpdateméthode pour être entièrement correct. Nous allons ignorer cela
pour le moment mais nous y reviendrons dans une section ultérieure de cette page.

Exemple d'utilisation de crochets


Voyons comment nous pourrions écrire ce composant avec des Hooks.
Vous pensez peut-être que nous aurions besoin d'un effet séparé pour effectuer le
nettoyage. Mais le code pour ajouter et supprimer un abonnement est si étroitement lié
qu'il useEffectest conçu pour le garder ensemble. Si votre effet renvoie une fonction, React
l'exécutera au moment du nettoyage :
import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);

useEffect(() => { function handleStatusChange(status) {


setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id,
handleStatusChange); // Specify how to clean up after this effect: return function
cleanup() { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
}; });
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}

Pourquoi avons-nous renvoyé une fonction de notre effet ? Il s'agit du mécanisme de


nettoyage facultatif des effets. Chaque effet peut retourner une fonction qui nettoie après
lui. Cela nous permet de garder la logique d'ajout et de suppression d'abonnements
proches les unes des autres. Ils font partie du même effet !
Quand exactement React nettoie-t-il un effet ? React effectue le nettoyage lorsque le
composant se démonte. Cependant, comme nous l'avons appris précédemment, les effets
s'exécutent pour chaque rendu et pas une seule fois. C'est pourquoi React
nettoie également les effets du rendu précédent avant d'exécuter les effets la prochaine
fois. Nous expliquerons pourquoi cela permet d'éviter les bogues et comment désactiver
ce comportement au cas où il créerait des problèmes de performances plus loin.
Noter
Nous n'avons pas à renvoyer une fonction nommée à partir de l'effet. Nous l'avons
appelé cleanupici pour clarifier son objectif, mais vous pouvez renvoyer une fonction fléchée
ou l'appeler différemment.

résumer
Nous avons appris que cela useEffectnous permet d'exprimer différents types d'effets
secondaires après le rendu d'un composant. Certains effets peuvent nécessiter un
nettoyage afin qu'ils renvoient une fonction :
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}

ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});

D'autres effets peuvent ne pas avoir de phase de nettoyage et ne rien renvoyer.


useEffect(() => {
document.title = `You clicked ${count} times`;
});

Le crochet d'effet unifie les deux cas d'utilisation avec une seule API.

Si vous sentez que vous avez une bonne compréhension du fonctionnement du


crochet d'effet, ou si vous vous sentez dépassé, vous pouvez passer à la page
suivante sur les règles des crochets maintenant.

Conseils d'utilisation des effets


Nous continuerons cette page avec un examen approfondi de certains aspects useEffectqui
intéresseront probablement les utilisateurs expérimentés de React. Ne vous sentez pas
obligé de les creuser maintenant. Vous pouvez toujours revenir sur cette page pour en
savoir plus sur le crochet d'effet.
Astuce : Utilisez plusieurs effets pour séparer les
préoccupations
L'un des problèmes que nous avons décrits dans Motivation for Hooks est que les
méthodes de cycle de vie de classe contiennent souvent une logique non liée, mais la
logique liée est divisée en plusieurs méthodes. Voici un composant qui combine la logique
du compteur et de l'indicateur de statut d'ami des exemples précédents :
class FriendStatusWithCounter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0, isOnline: null };
this.handleStatusChange = this.handleStatusChange.bind(this);
}

componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}

componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}

componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}

handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}
// ...

Notez comment la logique qui définit document.titleest divisée


entre componentDidMountet componentDidUpdate. La logique de souscription est également
répartie entre componentDidMountet componentWillUnmount. Et componentDidMountcontient du code
pour les deux tâches.
Alors, comment Hooks peut-il résoudre ce problème ? Tout comme vous pouvez utiliser
le State Hook plus d'une fois , vous pouvez également utiliser plusieurs effets. Cela nous
permet de séparer la logique non liée en différents effets :
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => { document.title = `You clicked ${count} times`;
});

const [isOnline, setIsOnline] = useState(null);


useEffect(() => { function handleStatusChange(status) {
setIsOnline(status.isOnline);
}

ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
// ...
}

Les crochets nous permettent de diviser le code en fonction de ce qu'il fait plutôt que
d'un nom de méthode de cycle de vie. React appliquera tous les effets utilisés par le
composant, dans l'ordre où ils ont été spécifiés.
Explication : Pourquoi les effets s'exécutent-ils à chaque
mise à jour ?
Si vous êtes habitué aux classes, vous vous demandez peut-être pourquoi la phase de
nettoyage des effets se produit après chaque nouveau rendu, et pas une seule fois lors du
démontage. Regardons un exemple pratique pour voir pourquoi cette conception nous
aide à créer des composants avec moins de bugs.
Plus tôt sur cette page , nous avons introduit un exemple FriendStatusde composant qui
indique si un ami est en ligne ou non. Notre classe lit friend.idà partir de this.props,
s'abonne au statut d'ami après le montage du composant et se désabonne pendant le
démontage :
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}

componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}

Mais que se passe-t-il si l' friendaccessoire change alors que le composant est à
l'écran ? Notre composant continuerait à afficher le statut en ligne d'un ami différent. C'est
un bogue. Nous provoquerions également une fuite de mémoire ou un plantage lors du
démontage, car l'appel de désabonnement utiliserait le mauvais identifiant d'ami.
Dans un composant de classe, nous aurions besoin d'ajouter componentDidUpdatepour gérer
ce cas :
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}

componentDidUpdate(prevProps) {
// Unsubscribe from the previous friend.id
ChatAPI.unsubscribeFromFriendStatus(prevProps.friend.id, this.handleStatusChange);
// Subscribe to the next friend.id
ChatAPI.subscribeToFriendStatus(this.props.friend.id, this.handleStatusChange);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}

Oublier de gérer componentDidUpdatecorrectement est une source courante de bogues dans


les applications React.
Considérons maintenant la version de ce composant qui utilise les crochets :
function FriendStatus(props) {
// ...
useEffect(() => {
// ...
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});

Il ne souffre pas de ce bug. (Mais nous n'y avons pas non plus apporté de modifications.)
Il n'y a pas de code spécial pour gérer les mises à jour car useEffectles gère par défaut . Il
nettoie les effets précédents avant d'appliquer les effets suivants. Pour illustrer cela, voici
une séquence d'appels d'abonnement et de désabonnement que ce composant pourrait
produire au fil du temps :
// Mount with { friend: { id: 100 } } props
ChatAPI.subscribeToFriendStatus(100, handleStatusChange); // Run first effect

// Update with { friend: { id: 200 } } props


ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // Clean up previous effect
ChatAPI.subscribeToFriendStatus(200, handleStatusChange); // Run next effect
// Update with { friend: { id: 300 } } props
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // Clean up previous effect
ChatAPI.subscribeToFriendStatus(300, handleStatusChange); // Run next effect

// Unmount
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // Clean up last effect

Ce comportement garantit la cohérence par défaut et évite les bogues courants dans les
composants de classe en raison d'une logique de mise à jour manquante.
Astuce : Optimiser les performances en sautant les effets
Dans certains cas, le nettoyage ou l'application de l'effet après chaque rendu peut créer un
problème de performances. Dans les composants de classe, nous pouvons résoudre ce
problème en écrivant une comparaison supplémentaire avec prevPropsou prevStateà
l'intérieur componentDidUpdate:
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
document.title = `You clicked ${this.state.count} times`;
}
}

Cette exigence est suffisamment courante pour être intégrée à l' useEffectAPI Hook. Vous
pouvez demander à React d' ignorer l'application d'un effet si certaines valeurs n'ont pas
changé entre les rendus. Pour cela, passez un tableau en second argument optionnel
à useEffect:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);

// Only re-run the effect if count changes

Dans l'exemple ci-dessus, nous passons [count]comme deuxième argument. Qu'est-ce que
ça veut dire? Si le countest 5, et que notre composant est restitué avec counttoujours égal
à 5, React comparera [5]le rendu précédent et [5]le rendu suivant. Étant donné que tous les
éléments du tableau sont identiques ( 5 === 5), React ignorerait l'effet. C'est notre
optimisation.
Lorsque nous rendons avec countupdated to 6, React comparera les éléments du [5]tableau
du rendu précédent aux éléments du [6]tableau du prochain rendu. Cette fois, React
réappliquera l'effet car 5 !== 6. S'il y a plusieurs éléments dans le tableau, React réexécutera
l'effet même si un seul d'entre eux est différent.
Cela fonctionne également pour les effets qui ont une phase de nettoyage :
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}

ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
}, [props.friend.id]); // Only re-subscribe if props.friend.id changes

À l'avenir, le deuxième argument pourrait être ajouté automatiquement par une


transformation au moment de la construction.
Noter
Si vous utilisez cette optimisation, assurez-vous que le tableau inclut toutes les valeurs de
la portée du composant (telles que les accessoires et l'état) qui changent au fil du
temps et qui sont utilisées par l'effet . Sinon, votre code référencera les valeurs
obsolètes des rendus précédents. En savoir plus sur la gestion des fonctions et sur la
marche à suivre lorsque le tableau change trop souvent .
Si vous souhaitez exécuter un effet et le nettoyer une seule fois (lors du montage et du
démontage), vous pouvez passer un tableau vide ( []) comme second argument. Cela
indique à React que votre effet ne dépend d' aucune valeur des accessoires ou de l'état, il
n'a donc jamais besoin d'être réexécuté. Ce n'est pas traité comme un cas particulier - cela
découle directement de la façon dont le tableau de dépendances fonctionne toujours.
Si vous passez un tableau vide ( []), les accessoires et l'état à l'intérieur de l'effet auront
toujours leurs valeurs initiales. Bien que le passage []comme deuxième argument soit plus
proche du modèle familier componentDidMountet componentWillUnmountmental, il existe
généralement de meilleures solutions pour éviter de répéter trop souvent les effets. De
plus, n'oubliez pas que React reporte son exécution useEffectjusqu'à ce que le navigateur
soit peint, donc faire un travail supplémentaire est moins problématique.
Nous vous recommandons d'utiliser la exhaustive-depsrègle dans le cadre de notre eslint-
plugin-react-hookspackage. Il avertit lorsque des dépendances sont spécifiées de manière
incorrecte et suggère un correctif.

Prochaines étapes
Toutes nos félicitations! C'était une longue page, mais j'espère qu'à la fin, la plupart de vos
questions sur les effets ont trouvé une réponse. Vous avez appris à la fois le crochet d'état
et le crochet d'effet, et vous pouvez faire beaucoup de choses avec les deux combinés. Ils
couvrent la plupart des cas d'utilisation des classes - et là où ils ne le font pas, vous
pourriez trouver les crochets supplémentaires utiles.
Nous commençons également à voir comment les crochets résolvent les problèmes décrits
dans Motivation . Nous avons vu comment le nettoyage des effets évite la duplication
dans componentDidUpdateet componentWillUnmount, rapproche le code associé et nous aide à
éviter les bogues. Nous avons également vu comment nous pouvons séparer les effets en
fonction de leur objectif, ce que nous ne pouvions pas du tout faire en classe.
À ce stade, vous vous demandez peut-être comment fonctionnent les crochets. Comment
React peut-il savoir quel useStateappel correspond à quelle variable d'état entre les
rendus ? Comment React "fait-il correspondre" les effets précédents et suivants sur chaque
mise à jour ? Sur la page suivante, nous découvrirons les règles des crochets - elles
sont essentielles pour faire fonctionner les crochets.

Vous aimerez peut-être aussi