État et cycle de vie
Cette page présente les concepts d’état local et de cycle de vie dans un composant React. Vous pouvez trouver la référence d’API des composants ici.
Prenons l’exemple de l’horloge dans une des sections précédentes. Dans Le rendu des éléments, nous avons appris une seule façon de mettre à jour l’interface utilisateur (UI). On appelle ReactDOM.render()
pour changer la sortie rendue :
function tick() {
const element = (
<div>
<h1>Bonjour, monde !</h1>
<h2>Il est {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render( element, document.getElementById('root') );}
setInterval(tick, 1000);
Dans cette section, nous allons apprendre à faire un composant Clock
vraiment réutilisable et isolé. Il mettra en place son propre minuteur et se mettra à jour tout seul à chaque seconde.
Nous commençons par isoler l’apparence de l’horloge :
function Clock(props) {
return (
<div> <h1>Bonjour, monde !</h1> <h2>Il est {props.date.toLocaleTimeString()}.</h2> </div> );
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />, document.getElementById('root')
);
}
setInterval(tick, 1000);
Cependant, il manque une contrainte cruciale : le fait que la Clock
mette en place le minuteur et mette à jour son interface utilisateur devrait être un détail d’implémentation de la Clock
.
Idéalement, on veut écrire ceci une seule fois et voir la Clock
se mettre à jour elle-même :
ReactDOM.render(
<Clock />, document.getElementById('root')
);
Pour implémenter ça, on a besoin d’ajouter un « état local » au composant Horloge
.
L’état local est similaire aux props, mais il est privé et complètement contrôlé par le composant.
Convertir une fonction en classe
Vous pouvez convertir un composant fonctionnel comme Clock
en une classe en cinq étapes :
- Créez une classe ES6, avec le même nom, qui étend
React.Component
. - Ajoutez-y une méthode vide appelée
render()
. - Déplacez le corps de la fonction dans la méthode
render()
. - Remplacez
props
parthis.props
dans le corps de la méthoderender()
. - Supprimez la déclaration désormais vide de la fonction.
class Clock extends React.Component {
render() {
return (
<div>
<h1>Bonjour, monde !</h1>
<h2>Il est {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
Le composant Clock
est maintenant défini comme une classe au lieu d’une fonction.
La méthode render
sera appelée à chaque fois qu’une mise à jour aura lieu, mais tant que l’on exploite le rendu de <Clock />
dans le même nœud DOM, une seule instance de la classe clock
sera utilisée. Cela nous permet d’utiliser des fonctionnalités supplémentaires telles que l’état local et les méthodes de cycle de vie.
Ajouter un état local à une classe
Nous allons déplacer la date
des props vers l’état en trois étapes :
- Remplacez
this.props.date
avecthis.state.date
dans la méthoderender()
:
class Clock extends React.Component {
render() {
return (
<div>
<h1>Bonjour, monde !</h1>
<h2>Il est {this.state.date.toLocaleTimeString()}.</h2> </div>
);
}
}
- Ajoutez un constructeur de classe qui initialise
this.state
:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()}; }
render() {
return (
<div>
<h1>Bonjour, monde !</h1>
<h2>Il est {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
Notez que l’on passe props
au constructeur de base :
constructor(props) {
super(props); this.state = {date: new Date()};
}
Les composants à base de classe devraient toujours appeler le constructeur de base avec props
.
- Supprimez la prop
date
de l’élément<Clock />
:
ReactDOM.render(
<Clock />, document.getElementById('root')
);
Nous rajouterons plus tard le code du minuteur dans le composant lui-même.
Le résultat ressemble à ceci :
class Clock extends React.Component {
constructor(props) { super(props); this.state = {date: new Date()}; }
render() {
return (
<div>
<h1>Bonjour, monde !</h1>
<h2>Il est {this.state.date.toLocaleTimeString()}.</h2> </div>
);
}
}
ReactDOM.render(
<Clock />, document.getElementById('root')
);
Ensuite, nous allons faire en sorte que le composant Clock
mette en place son propre minuteur et se mette à jour toutes les secondes.
Ajouter des méthodes de cycle de vie à une classe
Dans des applications avec de nombreux composants, il est très important de libérer les ressources utilisées par les composants quand ils sont détruits.
Nous voulons mettre en place un minuteur quand une Horloge
apparaît dans le DOM pour la première fois. Le terme React « montage » désigne cette phase.
Nous voulons également nettoyer le minuteur quand le DOM produit par l’Horloge
est supprimé. En React, on parle de « démontage ».
Nous pouvons déclarer des méthodes spéciales sur un composant à base de classe pour exécuter du code quand un composant est monté et démonté :
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() { }
componentWillUnmount() { }
render() {
return (
<div>
<h1>Bonjour, monde !</h1>
<h2>Il est {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
On les appelle des « méthodes de cycle de vie ».
La méthode componentDidMount()
est exécutée après que la sortie du composant a été injectée dans le DOM. C’est un bon endroit pour mettre en place le minuteur :
componentDidMount() {
this.timerID = setInterval( () => this.tick(), 1000 ); }
Notez qu’on a enregistré l’ID du minuteur directement sur this
(this.timerID
).
Alors que this.props
est mis en place par React lui-même et que this.state
a un sens bien spécial, vous pouvez très bien ajouter manuellement d’autres champs sur la classe si vous avez besoin de stocker quelque chose qui ne participe pas au flux de données (comme un ID de minuteur).
Nous allons détruire le minuteur dans la méthode de cycle de vie componentWillUnmount()
:
componentWillUnmount() {
clearInterval(this.timerID); }
Enfin, nous allons implémenter une méthode appelée tick()
que le composant Clock
va exécuter toutes les secondes.
Elle utilisera this.setState()
pour planifier une mise à jour de l’état local du composant :
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() { this.setState({ date: new Date() }); }
render() {
return (
<div>
<h1>Bonjour, monde !</h1>
<h2>Il est {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
Maintenant l’horloge se met à jour toutes les secondes.
Récapitulons ce qui se passe et l’ordre dans lequel les méthodes sont invoquées :
- Quand
<Clock />
est passé àReactDOM.render()
, React appelle le constructeur du composantClock
. PuisqueClock
a besoin d’afficher l’heure actuelle, il initialisethis.state
avec un objet contenant l’heure actuelle. Nous mettrons cet état à jour par la suite. - React appelle ensuite la méthode
render()
du composantClock
. C’est comme cela que React découvre ce qu’il faut afficher à l’écran. React met ensuite à jour le DOM pour correspondre à la sortie de la méthoderender()
du composantClock
. - Quand la sortie de la
Clock
est insérée dans le DOM, React appelle la méthode de cycle de viecomponentDidMount()
. À l’intérieur, le composantClock
demande au navigateur de mettre en place un minuteur pour appeler la méthodetick()
du composant une fois par seconde. - Chaque seconde, le navigateur appelle la méthode
tick()
. À l’intérieur, le composantClock
planifie une mise à jour de l’interface utilisateur en appelantsetState()
avec un objet contenant l’heure actuelle. Grâce à l’appel àsetState()
, React sait que l’état a changé, et invoque à nouveau la méthoderender()
pour savoir ce qui devrait être affiché à l’écran. Cette fois, la valeur dethis.state.date
dans la méthoderender()
est différente, la sortie devrait donc inclure l’heure mise à jour. React met à jour le DOM en accord avec cela. - Si le composant
Clock
finit par être retiré du DOM, React appellera la méthode de cycle de viecomponentWillUnmount()
pour que le minuteur soit arrêté.
Utiliser l’état local correctement
Il y’a trois choses que vous devriez savoir à propos de setState()
.
Ne modifiez pas l’état directement
Par exemple, ceci ne déclenchera pas un rafraîchissement du composant :
// Erroné
this.state.comment = 'Bonjour';
À la place, utilisez setState()
:
// Correct
this.setState({comment: 'Bonjour'});
Le seul endroit où vous pouvez affecter this.state
, c’est le constructeur.
Les mises à jour de l’état peuvent être asynchrones
React peut grouper plusieurs appels à setState()
en une seule mise à jour pour des raisons de performance.
Comme this.props
et this.state
peuvent être mises à jour de façon asynchrone, vous ne devez pas vous baser sur leurs valeurs pour calculer le prochain état.
Par exemple, ce code peut échouer pour mettre à jour un compteur :
// Erroné
this.setState({
counter: this.state.counter + this.props.increment,
});
Pour remédier à ce problème, utilisez la seconde forme de setState()
qui accepte une fonction à la place d’un objet. Cette fonction recevra l’état précédent comme premier argument et les props au moment de la mise à jour comme second argument :
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
Nous avons utilisé une fonction fléchée ci-dessus, mais une fonction normale marche aussi :
// Correct
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});
Les mises à jour de l’état sont fusionnées
Quand vous invoquez setState()
, React fusionne les objets que vous donnez avec l’état actuel.
Par exemple, votre état peut contenir plusieurs variables indépendantes :
constructor(props) {
super(props);
this.state = {
posts: [], comments: [] };
}
Ensuite, vous pouvez les mettre à jour indépendamment avec des appels séparés à setState()
:
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts });
});
fetchComments().then(response => {
this.setState({
comments: response.comments });
});
}
La fusion n’est pas profonde, donc this.setState({comments})
laisse this.state.posts
intacte, mais remplace complètement this.state.comments
.
Les données descendent
Ni parent ni enfant ne peuvent savoir si un certain composant est à état ou non, et ne devraient pas se soucier de savoir s’il est défini par une fonction ou une classe.
C’est pourquoi on dit souvent que l’état est local ou encapsulé. Il est impossible d’y accéder depuis un autre composant.
Un composant peut choisir de passer son état à ses enfants via des props :
<FormattedDate date={this.state.date} />
Le composant FormattedDate
reçoit la date
dans ses props et ne sait pas si elle vient de l’état de la Clock
, des props de la Clock
, ou a été tapée à la main :
function FormattedDate(props) {
return <h2>Il est {props.date.toLocaleTimeString()}.</h2>;
}
On appelle souvent cela un flux de données « du haut vers le bas » ou « unidirectionnel ». Un état local est toujours possédé par un composant spécifique, et toute donnée ou interface utilisateur dérivée de cet état ne peut affecter que les composants « en-dessous » de celui-ci dans l’arbre de composants.
Si vous imaginez un arbre de composants comme une cascade de props, chaque état de composant est une source d’eau supplémentaire qui rejoint la cascade à un point quelconque, mais qui coule également vers le bas.
Pour démontrer que tous les composants sont réellement isolés, nous pouvons créer un composant App
qui affiche trois <Clock>
s :
function Application() {
return (
<div>
<Clock /> <Clock /> <Clock /> </div>
);
}
ReactDOM.render(
<Application />,
document.getElementById('root')
);
Chaque Clock
met en place son propre minuteur et se met à jour indépendamment.
Dans une application React, le fait qu’un composant soit à état ou non est considéré comme un détail d’implémentation du composant qui peut varier avec le temps. Vous pouvez utiliser des composants sans état à l’intérieur de composants à état, et vice-versa.