L’état est un instantané
Vous trouvez peut-être que les variables d’état ressemblent à des variables JavaScript classiques, que vous pouvez lire et écrire librement. Pourtant, une variable d’état se comporte davantage comme une photo instantanée. Lui affecter une nouvelle valeur ne change pas la variable d’état que vous avez déjà, mais déclenche plutôt un nouveau rendu.
Vous allez apprendre
- Comment une modification d’état déclenche un nouveau rendu
- Comment et quand l’état est mis à jour
- Pourquoi l’état n’est pas mis à jour immédiatement après que vous l’avez modifié
- Comment les gestionnaires d’événements accèdent à un « instantané » de l’état
Modifier l’état déclenche un rendu
Vous pourriez croire que l’interface utilisateur (UI) évolue en réaction directe à des événements utilisateurs tels qu’un clic. Dans React, les choses sont un peu différentes de ce modèle mental. Dans la page précédente, nous avons vu que modifier l’état demande un nouveau rendu à React. Ça signifie que pour que l’interface réagisse à l’événement, vous devez mettre à jour l’état.
Dans cet exemple, lorsque vous appuyez sur « Envoyer », setIsSent(true)
demande à React de refaire un rendu de l’UI :
Error
Extra 185 of 186 byte(s) found at buffer[1]
Voici ce qui se passe lorsque vous cliquez sur le bouton :
- Le gestionnaire d’événement
onSubmit
est appelé. setIsSent(true)
définitisSent
àtrue
et planifie un nouveau rendu.- React refait le rendu du composant conformément à la nouvelle valeur de
isSent
.
Examinons de plus près la relation entre l’état et le rendu.
Le rendu prend une photo instantanée
« Faire le rendu » signifie que React appelle votre composant, qui est une fonction. Le JSX que cette fonction renvoie est comme une photo instantanée de l’UI à ce moment précis. Ses props, ses gestionnaires d’événements et ses variables locales ont tous été calculés en utilisant l’état au moment du rendu.
Contrairement à une photo ou une image de film, « l’instantané » de l’UI que vous renvoyez est interactif. Il comprend des éléments de logique tels que les gestionnaires d’événements qui décrivent ce qui doit arriver suite à des interactions. React met à jour l’écran pour refléter cet instantané et le connecte aux gestionnaires d’événements. Résultat, cliquer sur le bouton déclenchera le gestionnaire de clic défini dans votre JSX.
Lorsque React refait le rendu d’un composant :
- React rappelle votre fonction.
- Votre fonction renvoie un nouvel instantané JSX.
- React met alors à jour l’écran pour refléter l’instantané que votre fonction vient de renvoyer.
Illustré par Rachel Lee Nabors
Dans son rôle de mémoire du composant, l’état n’est pas comme une variable classique qui disparaît après que votre fonction a renvoyé son résultat. En réalité, l’état « vit » dans React lui-même — un peu comme sur une étagère ! — hors de votre fonction. Lorsque React appelle votre composant, il lui fournit un instantané de l’état pour ce rendu spécifique. Votre composant renvoie un instantané de l’UI avec un jeu tout frais de props et de gestionnaires d’événements dans son JSX, tous calculés en utilisant les valeurs de l’état pour ce rendu !
Illustré par Rachel Lee Nabors
Voici une petite expérience pour vous montrer comment ça fonctionne. Dans cet exemple, vous vous attendez peut-être à ce que cliquer sur le bouton « +3 » incrémente le compteur trois fois parce qu’il appelle setNumber(number + 1)
trois fois.
Voyez ce qui se passe lorsque vous cliquez sur le bouton « +3 » :
import { useState } from 'react'; export default function Counter() { const [number, setNumber] = useState(0); return ( <> <h1>{number}</h1> <button onClick={() => { setNumber(number + 1); setNumber(number + 1); setNumber(number + 1); }}>+3</button> </> ) }
Notez que number
n’est incrémenté qu’une fois par clic !
Définir l’état ne le change que pour le prochain rendu. Lors du rendu initial, number
était à 0
. C’est pourquoi, dans le gestionnaire onClick
de ce rendu-là, la valeur de number
est à 0
même après que setNumber(number + 1)
a été appelée :
<button onClick={() => {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
}}>+3</button>
Voici ce que le gestionnaire de clic de ce bouton dit à React de faire :
setNumber(number + 1)
:number
est à0
doncsetNumber(0 + 1)
.- React se prépare à modifier
number
à1
pour le prochain rendu.
- React se prépare à modifier
setNumber(number + 1)
:number
est à0
doncsetNumber(0 + 1)
.- React se prépare à modifier
number
à1
pour le prochain rendu.
- React se prépare à modifier
setNumber(number + 1)
:number
est à0
doncsetNumber(0 + 1)
.- React se prépare à modifier
number
à1
pour le prochain rendu.
- React se prépare à modifier
Même si vous appelez setNumber(number + 1)
trois fois, dans le gestionnaire d’événement de ce rendu number
est toujours à 0
, de sorte que vous le définissez à 1
trois fois. C’est pourquoi, après que le gestionnaire d’événement a terminé, React refait le rendu du composant avec number
égal à 1
plutôt qu’à 3
.
Vous pouvez visualiser ça mentalement en substituant les variables d’état par leurs valeurs dans votre code. Puisque la variable d’état number
est à 0
pour ce rendu, son gestionnaire d’événement a l’aspect suivant :
<button onClick={() => {
setNumber(0 + 1);
setNumber(0 + 1);
setNumber(0 + 1);
}}>+3</button>
Au prochain rendu, number
est à 1
, de sorte que le gestionnaire de clic pour ce rendu-là ressemble à ceci :
<button onClick={() => {
setNumber(1 + 1);
setNumber(1 + 1);
setNumber(1 + 1);
}}>+3</button>
C’est pourquoi re-cliquer sur le bouton amènera le compteur à 2
, puis à 3
au clic suivant, et ainsi de suite.
L’état au fil du temps
Eh bien, c’était amusant. Essayez de deviner ce qui s’affichera en cliquant sur ce bouton :
import { useState } from 'react'; export default function Counter() { const [number, setNumber] = useState(0); return ( <> <h1>{number}</h1> <button onClick={() => { setNumber(number + 5); alert(number); }}>+5</button> </> ) }
Si vous utilisez la même méthode de substitution que précédemment, vous pouvez deviner que l’alerte affichera « 0 » :
setNumber(0 + 5);
alert(0);
Mais si vous mettiez un timer sur l’alerte, de sorte qu’elle ne se déclenche qu’après que le composant a refait un rendu ? Dirait-elle « 0 » ou « 5 » ? Devinez !
import { useState } from 'react'; export default function Counter() { const [number, setNumber] = useState(0); return ( <> <h1>{number}</h1> <button onClick={() => { setNumber(number + 5); setTimeout(() => { alert(number); }, 3000); }}>+5</button> </> ) }
Ça vous surprend ? En utilisant la méthode de substitution, on voit bien que c’est « l’instantané » de l’état qui est passé à l’alerte :
setNumber(0 + 5);
setTimeout(() => {
alert(0);
}, 3000);
L’état stocké dans React a peut-être changé lorsque l’alerte finit par être exécutée, mais elle était planifiée en utilisant un instantané de l’état au moment de l’interaction utilisateur !
La valeur d’une variable d’état ne change jamais au sein d’un rendu, même si le code du gestionnaire d’événement est asynchrone. Au sein du onClick
de ce rendu, la valeur de number
continue à être à 0
même après que setNumber(number + 5)
a été appelée. Sa valeur est « figée » lorsque React « prend une photo » de l’UI en appelant votre composant.
Voici un exemple qui illustre en quoi ça réduit le potentiel d’erreur de chronologie dans vos gestionnaires d’événements. Vous trouverez ci-dessous un formulaire qui envoie un message avec un retard de cinq secondes. Imaginez le scénario suivant :
- Vous appuyez sur le bouton « Envoyer » pour envoyer « Bonjour » à Alice.
- Avant que les cinq secondes ne soient écoulées, vous modifiez le champ « Destinataire » à « Bob ».
À quel affichage vous attendez-vous ? Afficherait-il « Vous avez dit Bonjour à Alice » ? Ou plutôt « Vous avez dit Bonjour à Bob » ? Essayez de deviner sur base de ce que vous savez, puis tentez la manipulation :
import { useState } from 'react'; export default function Form() { const [to, setTo] = useState('Alice'); const [message, setMessage] = useState('Bonjour'); function handleSubmit(e) { e.preventDefault(); setTimeout(() => { alert(`Vous avez dit ${message} à ${to}`); }, 5000); } return ( <form onSubmit={handleSubmit}> <label> Destinataire :{' '} <select value={to} onChange={e => setTo(e.target.value)}> <option value="Alice">Alice</option> <option value="Bob">Bob</option> </select> </label> <textarea placeholder="Message" value={message} onChange={e => setMessage(e.target.value)} /> <button type="submit">Envoyer</button> </form> ); }
React conserve les valeurs d’état « figées » au sein des gestionnaires d’événements d’un rendu. Vous n’avez pas à vous soucier des éventuelles modifications ultérieures de l’état lorsque votre code s’exécute.
Et si vous souhaitiez plutôt lire la dernière valeur à jour d’un état avant de refaire un rendu ? Vous voudrez pour cela utiliser une fonction de mise à jour d’état, que nous découvrirons dans la prochaine page !
En résumé
- Modifier l’état demande un nouveau rendu.
- React stocke l’état hors de votre composant, comme sur une étagère.
- Lorsque vous appelez
useState
, React vous donne une photo instantanée de l’état pour ce rendu. - Les variables et gestionnaires d’événements ne « survivent » pas d’un rendu à l’autre. Chaque rendu a ses propres gestionnaires d’événements.
- Chaque rendu (et les fonctions qu’il contient) « verra » toujours l’instantané de l’état que React a fourni à ce rendu spécifique.
- Vous pouvez mentalement substituer l’état dans les gestionnaires d’événements, comme lorsque vous pensez au JSX produit par le rendu.
- Les gestionnaires d’événements créés par le passé continuent à voir les valeurs d’état spécifiques au rendu qui les a créés.
Défi 1 sur 1 · Implémenter un passage piéton
Voici un composant de signal de passage piéton qui bascule lorsqu’on appuie sur le bouton :
import { useState } from 'react'; export default function TrafficLight() { const [walk, setWalk] = useState(true); function handleClick() { setWalk(!walk); } return ( <> <button onClick={handleClick}> Passer à {walk ? 'Stop' : 'Traversez'} </button> <h1 style={{ color: walk ? 'darkgreen' : 'darkred' }}> {walk ? 'Traversez' : 'Stop'} </h1> </> ); }
Ajoutez un alert
au gestionnaire de clic. Lorsque le signal est vert et dit « Traversez », cliquer sur le bouton devrait dire « Stop arrive ». Lorsque le signal est rouge et dit « Stop », cliquer sur le bouton devrait dire « Traversez arrive ».
Appeler alert
avant ou après setWalk
fait-il une différence ?