Tutoriel : intro Ă React
These docs are old and wonât be updated. Go to react.dev for the new React docs.
The updated Tutorial teaches modern React and includes live examples.
Ce tutoriel ne présuppose aucune connaissance préalable de React.
Avant de commencer le tutoriel
Nous allons construire un petit jeu pendant ce tutoriel. Vous pourriez ĂȘtre tenté·e de lâignorer sous prĂ©texte que vous ne construisez pas de jeuxâmais donnez-lui sa chance. Les techniques que vous apprendrez dans ce tutoriel sont fondamentales pour la construction de nâimporte quel type dâappli React, et les maĂźtriser vous apportera une comprĂ©hension profonde de React.
Astuce
Ce tutoriel est conçu pour les personnes qui prĂ©fĂšrent apprendre en faisant. Si vous prĂ©fĂ©rez apprendre les concepts Ă partir de la base, jetez un Ćil Ă notre guide pas Ă pas. Vous constaterez peut-ĂȘtre que le guide et ce tutoriel sont complĂ©mentaires lâun Ă lâautre.
Le tutoriel est découpé en plusieurs sections :
- Mise en place du tutoriel vous donnera un point de départ pour suivre le tutoriel.
- Aperçu vous apprendra les fondamentaux de React : composants, props et état local.
- Finaliser le jeu vous apprendra les techniques les plus courantes de développement React.
- Ajouter du voyage dans le temps vous donnera une perception plus approfondie des forces particuliĂšres de React.
Il nâest pas nĂ©cessaire de complĂ©ter toutes les sections dâun coup pour tirer le meilleur parti de ce tutoriel. Essayez dâaller aussi loin que vous le pouvezâmĂȘme si ce nâest quâune ou deux sections.
Que construisons-nous ?
Dans ce tutoriel, nous allons voir comment construire un jeu de morpion interactif avec React.
Vous pouvez voir ce que ça va donner ici : rĂ©sultat final. Si le code vous semble obscur, ou si vous nâĂȘtes pas Ă lâaise avec la syntaxe du code, ne vous inquiĂ©tez pas ! Câest justement lâobjectif de ce tutoriel de vous aider Ă comprendre React et sa syntaxe.
Nous vous conseillons de jeter un coup dâĆil Ă ce jeu de morpion avant de continuer ce tutoriel. Une des fonctionnalitĂ©s que vous remarquerez, câest quâil affiche une liste numĂ©rotĂ©e sur la droite du plateau de jeu. Cette liste vous fournit un historique des tours de jeu, et elle est mise Ă jour au fil de lâeau.
Vous pouvez refermer le jeu de morpion une fois que vous en avez bien fait le tour. Nous commencerons par un gabarit plus simple pour ce tutoriel. Notre prochaine étape consiste à mettre le nécessaire en place, sur votre machine, pour que vous puissiez commencer à construire le jeu.
Prérequis
Nous supposerons que vous ĂȘtes un minimum Ă lâaise avec HTML et JavaScript, mais mĂȘme si vous venez dâun autre langage de programmation vous devriez ĂȘtre capable de suivre le dĂ©roulĂ©. Nous supposerons aussi que vous connaissez dĂ©jĂ les notions de programmation telles que les fonctions, objets, tableaux, et dans une moindre mesure, les classes.
Si vous avez besoin de rĂ©viser votre JavaScript, nous vous conseillons la lecture de ce guide. Remarquez que nous utilisons aussi certains aspects dâES6âune version rĂ©cente de JavaScript. Dans ce tutoriel, on utilise les fonctions flĂ©chĂ©es, les classes, et les instructions let
, et const
. Vous pouvez utiliser la REPL Babel pour examiner le résultat de la compilation de code ES6.
Mise en place du tutoriel
Il y a deux façons de suivre ce tutoriel : vous pouvez soit écrire le code dans votre navigateur, soit configurer un environnement de développement local sur votre ordinateur.
Option 1 : écrire le code dans le navigateur
Câest la façon la plus rapide de dĂ©marrer !
Tout dâabord, ouvrez ce code de dĂ©part dans un nouvel onglet. Lâonglet devrait alors afficher un plateau de jeu de morpion vide, et du code React. Nous modifierons celui-ci au fil de ce tutoriel.
Vous pouvez maintenant sauter la seconde option de mise en place, et aller directement à la section Aperçu pour faire un premier survol de React.
Option 2 : environnement de développement local
Câest une dĂ©marche complĂštement optionnelle, qui nâa rien dâobligatoire pour ce tutoriel !
Optionnel : instructions pour suivre le tuto localement dans votre éditeur de texte préféré
Cette mise en place requiert un peu plus de boulot mais vous permet de rĂ©aliser le tutoriel dans lâĂ©diteur de votre choix. Voici les Ă©tapes Ă suivre :
- Assurez-vous de disposer dâune version installĂ©e de Node.js suffisamment rĂ©cente.
- Suivez les instructions dâinstallation de Create React App pour crĂ©er un nouveau projet.
npx create-react-app my-app
- Supprimez tous les fichiers du dossier
src/
présent dans le nouveau projet.
Remarque
Ne supprimez pas le dossier
src
lui-mĂȘme, juste les fichiers sources Ă lâintĂ©rieur. Nous remplacerons les fichiers sources par dĂ©faut avec des exemples pour ce projet dans la prochaine Ă©tape.
cd my-app
cd src
# Si vous utilisez Mac ou Linux :
rm -f *
# Ou si vous ĂȘtes sur Windows :
del *
# Ensuite, revenez Ă la racine du projet
cd ..
- Ajoutez un fichier nommé
index.css
dans le dossiersrc/
et mettez-y ce code CSS. - Ajoutez un fichier nommé
index.js
dans le dossiersrc/
et mettez-y ce code JS. - Ajoutez les trois lignes suivantes tout en haut du
index.js
dans le dossiersrc/
:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
Vous pouvez maintenant exécuter npm start
dans le dossier du projet et ouvrir http://localhost:3000
dans votre navigateur, ce qui devrait vous afficher un plateau de morpion vide.
Nous vous conseillons de suivre ces instructions pour configurer la coloration syntaxique de votre éditeur.
Ă lâaide, je suis bloqué·e !
Si vous vous retrouvez bloqué·e, jetez un coup dâĆil aux ressources communautaires de support. Le chat Reactiflux, notamment, est super utile pour obtenir de lâaide rapidement. Si vous ne recevez pas de rĂ©ponse, ou si elle ne vous dĂ©bloque pas, merci de nous le signaler par une issue dans GitHub, et nous ferons de notre mieux pour vous aider.
Aperçu
Ă prĂ©sent que la mise en place est faite, faisons un tour dâhorizon de React !
Quâest-ce que React ?
React est une bibliothÚque JavaScript déclarative, efficace et flexible pour construire des interfaces utilisateurs (UI). Elle vous permet de composer des UI complexes à partir de petits morceaux de code isolés appelés « composants ».
React a plusieurs types distincts de composants, mais nous commencerons avec les sous-classes de React.Component
:
class ShoppingList extends React.Component {
render() {
return (
<div className="shopping-list">
<h1>Liste de courses pour {this.props.name}</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
<li>Oculus</li>
</ul>
</div>
);
}
}
// Exemple dâutilisation : <ShoppingList name="Marc" />
Nous parlerons de ces drĂŽles de balises façon XML dans un instant. Nous utilisons les composants pour dire Ă React ce que nous voulons voir Ă lâĂ©cran. Quand nos donnĂ©es changent, React fera une mise Ă jour optimale de nos composants et les rĂ©-affichera.
Ici, ShoppingList
est une classe de composant React, aussi appelée type de composant React. Un composant accepte des paramÚtres, appelés props
(qui est la contraction de « propriétés »), et renvoie via sa méthode render
une arborescence de vues Ă afficher.
La méthode render
renvoie une description de ce que vous voulez voir Ă lâĂ©cran. React prend cette description et affiche le rĂ©sultat. Plus spĂ©cifiquement, render
renvoie un Ă©lĂ©ment React, qui est une description lĂ©gĂšre de ce quâil faut afficher. La plupart des dĂ©veloppeurs React utilisent une syntaxe spĂ©ciale appelĂ©e « JSX », qui facilite lâĂ©criture de ces structures. La syntaxe <div />
est transformée à la compilation en React.createElement('div')
. Lâexemple ci-dessus est en fait Ă©quivalent Ă :
return React.createElement('div', {className: 'shopping-list'},
React.createElement('h1', /* ... enfants de h1 ... */),
React.createElement('ul', /* ... enfants de ul ... */)
);
Voir la version compilée complÚte.
Si vous ĂȘtes curieux·se, createElement()
est dĂ©crite en dĂ©tail dans la rĂ©fĂ©rence de lâAPI, mais nous ne lâutiliserons pas dans ce tutoriel. On prĂ©fĂ©rera plutĂŽt utiliser JSX.
JSX conserve toute la puissance de JavaScript. Vous pouvez mettre nâimporte quelle expression JavaScript entre accolades dans du JSX. Chaque Ă©lĂ©ment React est un objet JavaScript que vous pouvez stocker dans une variable ou passer de main en main dans votre programme.
Le composant ShoppingList
ci-dessus nâexploite que des composants prĂ©dĂ©finis du DOM tels que <div />
et <li />
. Mais vous pouvez également composer et afficher des composants React personnalisés. Par exemple, nous pouvons désormais faire référence à la liste de courses complÚte en écrivant simplement <ShoppingList />
. Chaque composant React est encapsulĂ© (isolĂ©) et peut fonctionner indĂ©pendamment du reste ; câest ce qui vous permet de construire des UI complexes Ă partir de composants simples.
Examiner le code de départ
Si vous comptez suivre le tutoriel dans votre navigateur, ouvrez ce code dans un nouvel onglet : code de démarrage. Si vous avez choisi de travailler localement, ouvrez plutÎt src/index.js
dans votre dossier projet (vous avez déjà manipulé ce fichier pendant la mise en place).
Ce code de dĂ©marrage constitue la base de ce que nous allons construire. Nous avons fourni la mise en forme CSS afin que vous puissiez vous concentrer sur lâapprentissage de React et la programmation du jeu de morpion.
En examinant le code, vous remarquerez que nous avons trois composants React :
Square
Board
Game
Le composant Square
affiche un unique <button>
et le Board
affiche 9 cases. Le composant Game
affiche un plateau avec des valeurs temporaires que nous modifierons plus tard. Ă ce stade, aucun composant nâest interactif.
Passer des données via les props
Histoire de prendre la température, essayons de passer des données de notre composant Board
Ă notre composant Square
.
Vous pouvez tout Ă fait copier-coller le code au fil du tutoriel, mais nous vous conseillons de le taper vous-mĂȘme. Cela vous aidera Ă dĂ©velopper une mĂ©moire musculaire et une meilleure comprĂ©hension.
Dans la méthode renderSquare
de Board
, modifiez le code pour passer une prop appelée value
au Square
:
class Board extends React.Component {
renderSquare(i) {
return <Square value={i} />; }
}
Modifiez ensuite la méthode render
de Square
pour quâelle affiche cette valeur en remplaçant {/* TODO */}
par {this.props.value}
:
class Square extends React.Component {
render() {
return (
<button className="square">
{this.props.value} </button>
);
}
}
Avant :
AprÚs : vous devriez voir un nombre dans chaque carré affiché.
Voir le code complet Ă ce stade
FĂ©licitations ! Vous venez de « passer une prop » dâun composant parent Board
Ă un composant enfant Square
. Dans les applis React, câest grĂące au passage de props que lâinformation circule, toujours des parents vers les enfants.
Réaliser un composant interactif
Faisons maintenant en sorte de remplir le composant Square
avec un « X » quand on clique dessus.
Pour commencer, modifiez la balise button
renvoyée par la méthode render()
du composant Square
pour aboutir Ă ceci :
class Square extends React.Component {
render() {
return (
<button className="square" onClick={function() { console.log('clic'); }}> {this.props.value}
</button>
);
}
}
Désormais, si vous cliquez sur un Square
, vous devriez obtenir une alerte dans votre navigateur.
Remarque
Pour économiser de la frappe et éviter certains aspects déroutants de
this
, nous utiliserons dĂ©sormais la syntaxe des fonctions flĂ©chĂ©es pour les gestionnaires dâĂ©vĂ©nements :class Square extends React.Component { render() { return ( <button className="square" onClick={() => console.log('clic')}> {this.props.value} </button> ); } }
Remarquez que dans
onClick={() => alert('click')}
, nous passons une fonction Ă la proponClick
. React ne lâappellera que suite Ă un clic. Une erreur courante consiste Ă oublier le() =>
de départ, pour écrire seulementonClick={alert('click')}
: lâalerte se dĂ©clencherait alors immĂ©diatement, Ă chaque affichage.
Pour lâĂ©tape suivante, nous voulons que le composant Square
« se souvienne » quâon lui a cliquĂ© dessus, et se remplisse alors avec la marque « X ». Afin quâils puissent « se souvenir » de choses, les composants utilisent lâĂ©tat local.
Les composants React peuvent définir un état local en définissant this.state
dans leurs constructeurs. this.state
est considéré comme une donnée privée du composant React qui le définit. Stockons donc la valeur courante du Square
dans this.state
, et changeons-la quand on clique sur la case.
Dans un premier temps, nous allons ajouter un constructeur Ă la classe pour initialiser lâĂ©tat local :
class Square extends React.Component {
constructor(props) { super(props); this.state = { value: null, }; }
render() {
return (
<button className="square" onClick={() => console.log('clic')}>
{this.props.value}
</button>
);
}
}
Remarque
Dans les classes JavaScript, vous devez toujours appeler
super
quand vous dĂ©finissez le constructeur dâune sous-classe. Tous les composants React Ă base de classes qui ont leur propreconstructor
devraient le faire dĂ©marrer par un appel Ăsuper(props)
.
Nous pouvons maintenant modifier la méthode render
de Square
pour afficher la valeur de lâĂ©tat courant lorsquâon clique dessus :
- Remplacez
this.props.value
parthis.state.value
dans la balise<button>
. - Remplacez le gestionnaire dâĂ©vĂ©nements
onClick={...}
paronClick={() => this.setState({value: 'X'})}
. - Mettez les props
className
etonClick
sur des lignes distinctes pour une meilleure lisibilité.
Une fois ces changements effectués, la balise <button>
renvoyée par la méthode render
de Square
devrait ressembler Ă ceci :
class Square extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null,
};
}
render() {
return (
<button
className="square" onClick={() => this.setState({value: 'X'})} >
{this.state.value} </button>
);
}
}
En appelant this.setState
depuis un gestionnaire onClick
dans la méthode render
de Square
, nous demandons à React de ré-afficher ce Square
dĂšs que le <button>
est cliqué. AprÚs la mise à jour, la this.state.value
du Square
sera 'X'
, et nous verrons donc X
sur le plateau de jeu. Si vous cliquez sur nâimporte quel carrĂ©, un X
devrait sây afficher.
Quand vous appelez setState
dans un composant, React met aussi automatiquement Ă jour les composants enfants au sein de celui-ci.
Voir le code complet Ă ce stade
Outils de développement
Lâextension React Devtools pour Chrome et Firefox vous permet dâexaminer une arborescence de composants React dans les outils de dĂ©veloppement de votre navigateur.

Avec les React DevTools, vous pouvez examiner les props et lâĂ©tat local de vos composants React.
AprĂšs avoir installĂ© React DevTools, vous pouvez faire un clic droit sur nâimporte quel Ă©lĂ©ment de la page, cliquer sur « Inspecter » (ou « Examiner lâĂ©lĂ©ment ») pour ouvrir les outils de dĂ©veloppement, et les onglets React (ââïž Componentsâ et ââïž Profilerâ) devraient ĂȘtre les derniers onglets sur la droite. Utilisez ââïž Componentsâ pour inspecter lâarbre de composants.
Ceci dit, vous aurez quelques manipulations en plus Ă faire si vous utilisez CodePen :
- Connectez-vous ou inscrivez-vous et confirmez votre adresse e-mail (obligatoire pour éviter le spam).
- Cliquez sur le bouton âForkâ.
- Cliquez sur âChange Viewâ et choisissez âDebug modeâ.
- Dans le nouvel onglet qui sâouvre alors, les outils de dĂ©veloppement devrait proposer lâonglet React.
Finaliser le jeu
Nous disposons dĂ©sormais des blocs de base pour construire notre jeu de morpion. Pour aboutir Ă un jeu complet, nous avons besoin dâalterner le placement de « X » et de « O » sur la plateau, et de trouver un moyen de dĂ©terminer qui gagne.
Faire remonter lâĂ©tat
Pour le moment, chaque composant Square
maintient sa part de lâĂ©tat du jeu. Pour vĂ©rifier si la partie est gagnĂ©e, nous devons plutĂŽt maintenir lâĂ©tat des 9 cases dans un endroit unique.
On pourrait penser que Board
nâa quâĂ demander Ă chaque Square
quel est son Ă©tat. MĂȘme si cette approche est possible en React, nous la dĂ©conseillons car un tel code devient vite difficile Ă comprendre et Ă refactoriser, et offre un terrain fertile aux bugs. Au lieu de ça, la meilleure approche consiste Ă stocker lâĂ©tat du jeu dans le composant Board
parent plutĂŽt que dans chaque Square
. Le composant Board
peut alors dire Ă chaque Square
quoi afficher en lui passant une prop, exactement comme nous lâavions fait en passant un nombre Ă chaque Square
.
Pour rĂ©cupĂ©rer les donnĂ©es dâenfants multiples, ou pour permettre Ă deux composants enfants de communiquer entre eux, il vous faut plutĂŽt dĂ©clarer leur Ă©tat partagĂ© dans le composant parent. Ce composant parent peut alors leur repasser cet Ă©tat au travers des props ; ainsi, les composants enfants sont synchronisĂ©s entre eux et avec le composant parent.
Il est courant de faire remonter lâĂ©tat vers le composant parent lorsquâon refactorise des composants Reactâprofitons de cette opportunitĂ© pour essayer.
Ajoutez un constructeur au Board
et dĂ©finissez son Ă©tat initial Ă raison dâun tableau de 9 null
s, qui correspondent aux 9 cases :
class Board extends React.Component {
constructor(props) { super(props); this.state = { squares: Array(9).fill(null), }; }
renderSquare(i) {
return <Square value={i} />;
}
Lorsque nous remplirons le plateau par la suite, il ressemblera Ă ceci :
[
'O', null, 'X',
'X', 'X', 'O',
'O', null, null,
]
La méthode renderSquare
du Board
ressemble actuellement à ça :
renderSquare(i) {
return <Square value={i} />;
}
Au début, nous passions la prop value
depuis le Board
pour afficher des nombres de 0 Ă 8 dans chaque Square
. Lors dâune Ă©tape ultĂ©rieure, nous avions remplacĂ© les nombres par des marques « X » dĂ©finies par lâĂ©tat local de chaque Square
. Câest pourquoi Square
ignore complĂštement, Ă ce stade, la prop value
qui lui est passée par le Board
.
Nous allons recommencer à utiliser le mécanisme de passage de props. Commençons par modifier le Board
afin quâil indique Ă chaque Square
sa valeur actuelle ('X'
, 'O'
ou null
). Nous avons déjà défini le tableau squares
dans le constructeur de Board
, il ne nous reste quâĂ modifier sa mĂ©thode renderSquare
pour quâelle y lise lâinformation :
renderSquare(i) {
return <Square value={this.state.squares[i]} />; }
Voir le code complet Ă ce stade
Chaque Square
reçoit désormais une prop value
qui vaudra 'X'
, 'O'
, ou null
pour les cases vides.
Ensuite, il nous faut changer la façon de réagir aux clics sur un Square
. Câest dĂ©sormais le composant Board
qui maintient lâinformation de remplissage des cases. Nous devons donc trouver un moyen pour que Square
mette Ă jour lâĂ©tat local de Board
. Dans la mesure oĂč lâĂ©tat local est considĂ©rĂ© privĂ©, rĂ©servĂ© au composant qui le dĂ©finit, nous ne pouvons pas mettre cet Ă©tat Ă jour directement depuis Square
.
Au lieu de ça, nous allons passer une fonction de Board
au Square
, qui sera appelée par Square
en réponse aux clics. Modifions la méthode renderSquare
de Board
en conséquence :
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)} />
);
}
Remarque
Nous avons dĂ©coupĂ© lâĂ©lĂ©ment renvoyĂ© sur plusieurs lignes pour des raisons de lisibilitĂ©, et ajoutĂ© des parenthĂšses pour que JavaScript ne considĂšre pas le
return
comme autonome et renvoyantundefined
, ce qui casserait notre code.
Nous passons désormais deux props de Board
Ă Square
: value
et onClick
. La prop onClick
est une fonction que Square
appellera quand on clique dessus. Apportez les modifications suivantes Ă Square
:
- Remplacez
this.state.value
parthis.props.value
dans la méthoderender
deSquare
- Remplacez
this.setState()
parthis.props.onClick()
dans la méthoderender
deSquare
- Supprimez le
constructor
deSquare
, puisquâil ne maintient plus son propre Ă©tat local
Une fois ces modifications effectuées, le composant Square
devrait ressembler Ă ceci :
class Square extends React.Component { render() { return (
<button
className="square"
onClick={() => this.props.onClick()} >
{this.props.value} </button>
);
}
}
Quand on clique sur un Square
, la fonction onClick
fournie par le Board
est appelée. Voici un rappel de ce que nous avons fait pour cela :
- La prop
onClick
du composant DOM natif<button>
indique Ă React de mettre en place un gestionnaire dâĂ©vĂ©nements pour les clics. - Quand on cliquera sur le bouton, React appellera le gestionnaire dâĂ©vĂ©nements
onClick
défini dans la méthoderender()
deSquare
. - Ce gestionnaire dâĂ©vĂ©nements appelle
this.props.onClick()
. La proponClick
deSquare
a été spécifiée par leBoard
. - Puisque le
Board
a passéonClick={() => this.handleClick(i)}
ĂSquare
, ce dernier appelle en faitthis.handleClick(i)
(dans le contexte deBoard
) lors du clic. - Nous nâavons pas encore dĂ©fini la mĂ©thode
handleClick()
, du coup notre code plante. Si vous cliquez sur une case Ă ce stade, vous devriez voir un Ă©cran rouge dâerreur qui dit quelque chose comme âthis.handleClick is not a functionâ.
Remarque
Lâattribut
onClick
de lâĂ©lĂ©ment DOM<button>
a un sens particulier pour React, car il sâagit ici dâun composant « natif ». Pour les composants personnalisĂ©s tels queSquare
, vous avez toute latitude dans le nommage des props. On aurait pu nommer autrement la proponClick
deSquare
ou la méthodehandleClick
deBoard
, le code marcherait toujours. En React, une convention répandue consiste à utiliser des nomson[Event]
pour les props qui représentent des événements, ethandle[Event]
pour les méthodes qui gÚrent ces événements.
Lorsque nous cliquons sur un Square
, nous devrions obtenir une erreur parce que nous nâavons pas encore dĂ©fini handleClick
. Nous allons donc lâajouter dans la classe Board
:
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
};
}
handleClick(i) { const squares = this.state.squares.slice(); squares[i] = 'X'; this.setState({squares: squares}); }
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
);
}
render() {
const status = 'Prochain joueur : X';
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
Voir le code complet Ă ce stade
Avec ces ajustements, nous pouvons Ă nouveau cliquer sur les cases pour les remplir, comme avant. Mais maintenant, lâĂ©tat est stockĂ© dans le composant Board
au lieu des composants Square
individuels. Quand lâĂ©tat du Board
change, les composants Square
sont automatiquement rafraĂźchis. Conserver lâĂ©tat de lâensemble des cases dans le composant Board
lui permettra plus tard de déterminer un vainqueur.
Dans la mesure oĂč les composants Square
ne maintiennent plus dâĂ©tat, ils reçoivent leurs valeurs du composant Board
et lâinforment lorsquâon clique sur eux. En termes React, les composants Square
sont des composants contrÎlés. Le Board
dispose dâun contrĂŽle complet sur eux.
Remarquez que dans handleClick
, nous appelons .slice()
pour créer une copie du tableau squares
à modifier, plutÎt que de modifier le tableau existant. Nous expliquerons pourquoi cette copie est nécessaire dans la prochaine section.
Pourquoi lâimmutabilitĂ© est importante
Dans lâexemple de code prĂ©cĂ©dent, nous vous suggĂ©rions dâutiliser la mĂ©thode .slice()
pour créer une copie du tableau squares
Ă modifier, au lieu de travailler directement sur le tableau existant. Nous allons maintenant parler dâimmutabilitĂ©, et des raisons pour lesquelles câest un sujet qui mĂ©rite dâĂȘtre appris.
Il y a en gĂ©nĂ©ral deux approches Ă la modification de donnĂ©es. La premiĂšre consiste Ă muter les donnĂ©es en altĂ©rant directement leurs valeurs. La seconde prĂ©fĂšre remplacer les donnĂ©es dâorigine par une nouvelle copie, porteuse des modifications dĂ©sirĂ©es.
Modification de données par mutation
var player = {score: 1, name: 'Jérémie'};
player.score = 2;
// Désormais player vaut {score: 2, name: 'Jérémie'}
Modification de données sans mutation
var player = {score: 1, name: 'Jérémie'};
var newPlayer = Object.assign({}, player, {score: 2});
// à ce stade player est intact, mais newPlayer vaut {score: 2, name: 'Jérémie'}
// Ou si vous utilisez la proposition de syntaxe ES2018
// âObject Rest/Spread Propertiesâ, vous pouvez Ă©crire :
//
// var newPlayer = {...player, score: 2};
Le rĂ©sultat final est le mĂȘme, mais en refusant de muter (ou dâaltĂ©rer les donnĂ©es sous-jacentes) directement la donnĂ©e dâorigine, vous bĂ©nĂ©ficiez dâun certain nombre dâavantages dĂ©crits ci-dessous.
Des fonctionnalités complexes deviennent simples
LâimmutabilitĂ© facilite considĂ©rablement lâimplĂ©mentation de fonctionnalitĂ©s complexes. Plus tard dans ce tutoriel, nous implĂ©menterons une fonction de « voyage dans le temps », qui nous permet de consulter lâhistorique de la partie de morpion et de « revenir » Ă des tours prĂ©cĂ©dents de notre choix. Cette fonctionnalitĂ© nâest pas spĂ©cifique aux jeuxâla capacitĂ© Ă annuler et refaire certaines actions est un besoin rĂ©current dans les applications. Ăviter les mutations directes nous permet de conserver intactes les versions prĂ©cĂ©dentes de lâhistorique, pour les rĂ©utiliser par la suite.
Détecter les modifications
DĂ©tecter les modifications dâobjets mutables est une tĂąche difficile, car ils sont modifiĂ©s directement. Une telle dĂ©tection exigerait de comparer lâobjet mutable Ă des copies prĂ©cĂ©dentes de son contenu et de toute lâarborescence des objets internes quâil pourrait contenir.
En revanche, il est facile de dĂ©tecter la modification dâobjet immuables : si la rĂ©fĂ©rence sur lâobjet immuable dont on dispose diffĂšre de la prĂ©cĂ©dente, alors lâobjet a changĂ©.
Déterminer quand déclencher un nouveau rendu dans React
Le principal avantage de lâimmutabilitĂ© pour React, câest quâelle permet la construction de composants purs. Des donnĂ©es immuables facilitent la dĂ©tection des modifications, ce qui Ă son tour permet de dĂ©terminer quâun composant doit ĂȘtre rafraĂźchi.
Vous pouvez en apprendre davantage sur shouldComponentUpdate()
et la construction de composants purs en lisant Optimiser les performances.
Fonctions composants
Nous allons transformer Square
pour en faire une fonction composant.
Dans React, les fonctions composants constituent une maniĂšre plus simple dâĂ©crire des composants qui ne contiennent quâune mĂ©thode render
et nâont pas leur propre Ă©tat. Au lieu de dĂ©finir une sous-classe de React.Component
, nous pouvons écrire une fonction qui prendra les props
en argument, et renverra ce qui devrait ĂȘtre affichĂ©. Les fonctions composants sont moins fastidieuses Ă Ă©crire que les classes, et de nombreux composants peuvent ĂȘtre exprimĂ©s ainsi.
Remplacez la classe Square
par cette fonction :
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
Nous avons changé this.props
en props
pour ses deux occurrences.
Voir le code complet Ă ce stade
Remarque
En modifiant
Square
pour en faire une fonction composant, nous avons aussi abrégéonClick={() => this.props.onClick()}
enonClick={props.onClick}
(remarquez lâabsence des parenthĂšses dâappel des deux cĂŽtĂ©s).
Jouer tour Ă tour
Nous avons maintenant besoin de corriger un défaut évident de notre jeu de morpion : il est pour le moment impossible de poser des marques « O » sur le plateau.
Disons que le premier coup utilisera « X » par dĂ©faut. Nous pouvons alors implĂ©menter cette dĂ©cision en modifiant lâĂ©tat initial du constructeur de Board
:
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
xIsNext: true, };
}
Chaque fois quâun joueur interviendra, xIsNext
(un boolĂ©en) sera basculĂ© afin de dĂ©terminer Ă qui appartiendra le prochain tour, et lâĂ©tat du jeu sera sauvegardĂ©. Mettons Ă jour la fonction handleClick
de Board
pour basculer la valeur de xIsNext
:
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = this.state.xIsNext ? 'X' : 'O'; this.setState({
squares: squares,
xIsNext: !this.state.xIsNext, });
}
Grùce à cette modification, les « X » et les « O » alternent désormais. Essayez !
Modifions aussi le texte de « statut » dans le render
du Board
pour quâil affiche quel joueur a le prochain tour :
render() {
const status = 'Prochain joueur : ' + (this.state.xIsNext ? 'X' : 'O');
return (
// Le reste nâa pas changĂ©
Ces modifications effectuées, vous devriez aboutir à ce composant Board
:
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
xIsNext: true, };
}
handleClick(i) {
const squares = this.state.squares.slice(); squares[i] = this.state.xIsNext ? 'X' : 'O'; this.setState({ squares: squares, xIsNext: !this.state.xIsNext, }); }
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
);
}
render() {
const status = 'Prochain joueur : ' + (this.state.xIsNext ? 'X' : 'O');
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
Voir le code complet Ă ce stade
Déclarer un vainqueur
Ă prĂ©sent que nous affichons Ă qui est le prochain tour, nous devrions aussi indiquer si la partie est gagnĂ©e, ou sâil nây a plus de coups Ă jouer. Copiez cette fonction utilitaire et collez-la Ă la fin du fichier :
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
Ă partir dâun tableau de 9 cases, cette fonction vĂ©rifiera si on a un gagnant et renverra 'X'
, 'O'
ou null
suivant le cas.
Nous appellerons calculateWinner(squares)
dans la méthode render
du Board
, pour vĂ©rifier si un joueur a gagnĂ©. Si câest le cas, nous afficherons un texte du style « X a gagnĂ© » ou « O a gagnĂ© ». Remplaçons la dĂ©claration de status
dans la méthode render
de Board
par ce code :
render() {
const winner = calculateWinner(this.state.squares); let status; if (winner) { status = winner + ' a gagné'; } else { status = 'Prochain joueur : ' + (this.state.xIsNext ? 'X' : 'O'); }
return (
// Le reste nâa pas changĂ©
Nous pouvons maintenant modifier la méthode handleClick
de Board
pour la court-circuiter en ignorant le clic si quelquâun a dĂ©jĂ gagnĂ© la partie, ou si la case est dĂ©jĂ remplie :
handleClick(i) {
const squares = this.state.squares.slice();
if (calculateWinner(squares) || squares[i]) { return; } squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
});
}
Voir le code complet Ă ce stade
Bravo ! Vous avez maintenant un jeu de morpion opĂ©rationnel. Et vous venez dâapprendre les bases de React au passage. Du coup, câest probablement vous qui avez gagnĂ© sur ce coup.
Ajouter du voyage dans le temps
à titre de bonus, faisons en sorte de pouvoir « revenir dans le passé » vers des tours de jeu précédents.
Stocker un historique des mouvements
Si nous avions modifié directement le tableau squares
, il aurait Ă©tĂ© trĂšs difficile dâimplĂ©menter ce voyage dans le temps.
Seulement voilà , nous avons utilisé slice()
pour créer une nouvelle copie du tableau squares
Ă chaque tour, et lâavons traitĂ©e comme une donnĂ©e immuable. Ăa va nous permettre de stocker chaque version passĂ©e du tableau squares
, et de naviguer entre les tours ayant déjà eu lieu.
Nous allons stocker les tableaux squares
passés dans un autre tableau appelé history
. Le tableau history
représente tous les états du plateau, du premier au dernier tour, et sa forme ressemble à ça :
history = [
// Avant le premier tour
{
squares: [
null, null, null,
null, null, null,
null, null, null,
]
},
// AprĂšs le premier tour
{
squares: [
null, null, null,
null, 'X', null,
null, null, null,
]
},
// AprĂšs le deuxiĂšme tour
{
squares: [
null, null, null,
null, 'X', null,
null, null, 'O',
]
},
// ...
]
Il nous faut maintenant dĂ©cider quel composant devrait gĂ©rer lâĂ©tat history
.
Faire remonter lâĂ©tat, encore
Nous voulons que le composant racine Game
affiche la liste des tours passĂ©s. Il aura besoin dâun accĂšs Ă history
pour ça, du coup câest dans ce composant racine Game
que nous allons placer notre état history
.
Ce choix nous permet de retirer lâĂ©tat squares
du composant enfant Board
. Tout comme nous avions « fait remonter lâĂ©tat » du composant Square
vers le composant Board
, nous le faisons Ă nouveau remonter de Board
vers le composant racine Game
. Ăa donne Ă Game
un contrÎle total sur les données du Board
, et lui permet de demander Ă Board
dâafficher un tour prĂ©cĂ©dent issu de history
.
Pour commencer, dĂ©finissons lâĂ©tat initial du composant Game
au sein de son constructeur :
class Game extends React.Component {
constructor(props) { super(props); this.state = { history: [{ squares: Array(9).fill(null), }], xIsNext: true, }; }
render() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<div>{/* status */}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
);
}
}
Ensuite, nous devons faire en sorte que le composant Board
reçoive des props squares
et onClick
depuis le composant Game
. Dans la mesure oĂč nous avons maintenant un unique gestionnaire de clics quelle que soit la case, nous lui passerons la position de la case concernĂ©e, afin quâil sache de laquelle il sâagit. Voici les Ă©tapes Ă suivre :
- Effacez le
constructor
deBoard
. - Remplacez
this.state.squares[i]
parthis.props.squares[i]
dans la méthoderenderSquare
deBoard
. - Remplacez
this.handleClick(i)
parthis.props.onClick(i)
dans la méthoderenderSquare
deBoard
.
Le composant Board
devrait désormais ressembler à ceci :
class Board extends React.Component {
handleClick(i) {
const squares = this.state.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
});
}
renderSquare(i) {
return (
<Square
value={this.props.squares[i]} onClick={() => this.props.onClick(i)} />
);
}
render() {
const winner = calculateWinner(this.state.squares);
let status;
if (winner) {
status = winner + ' a gagné';
} else {
status = 'Prochain joueur : ' + (this.state.xIsNext ? 'X' : 'O');
}
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
Mettons maintenant à jour la méthode render
du composant Game
pour quâelle utilise la plus rĂ©cente entrĂ©e de lâhistorique et affiche lâĂ©tat du jeu :
render() {
const history = this.state.history; const current = history[history.length - 1]; const winner = calculateWinner(current.squares); let status; if (winner) { status = winner + ' a gagné'; } else { status = 'Prochain joueur : ' + (this.state.xIsNext ? 'X' : 'O'); }
return (
<div className="game">
<div className="game-board">
<Board squares={current.squares} onClick={(i) => this.handleClick(i)} /> </div>
<div className="game-info">
<div>{status}</div> <ol>{/* TODO */}</ol>
</div>
</div>
);
}
Dans la mesure oĂč câest dĂ©sormais le composant Game
qui affiche lâĂ©tat du jeu, nous pouvons retirer le code correspondant de la mĂ©thode render
du Board
. AprÚs cette refactorisation, la méthode devrait ressembler à ça :
render() { return ( <div> <div className="board-row"> {this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
Pour finir, nous devons déplacer la méthode handleClick
du composant Board
dans le composant Game
. Nous avons aussi besoin de la modifier parce que lâĂ©tat du composant Game
a une structure différente. Au sein de la méthode handleClick
de Game
, nous allons par ailleurs concatĂ©ner les nouvelles entrĂ©es dâhistorique Ă history
.
handleClick(i) {
const history = this.state.history; const current = history[history.length - 1]; const squares = current.squares.slice(); if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
history: history.concat([{ squares: squares, }]), xIsNext: !this.state.xIsNext,
});
}
Remarque
Contrairement à la méthode
push()
des tableaux, que vous connaissez peut-ĂȘtre mieux, la mĂ©thodeconcat()
ne modifie pas le tableau dâorigine, ce qui est prĂ©fĂ©rable pour nous.
Ă ce stade, le composant Board
nâa besoin que de ses mĂ©thodes renderSquare
et render
. LâĂ©tat du jeu et la mĂ©thode handleClick
devraient ĂȘtre dans le composant Game
.
Voir le code complet Ă ce stade
Afficher les mouvements passés
Puisque nous gardons trace de lâhistorique de notre jeu de morpion, nous pouvons dĂ©sormais lâafficher Ă lâutilisateur, sous forme dâune liste des tours passĂ©s.
Nous avons appris plus tĂŽt que les Ă©lĂ©ments React sont de simples objets JavaScript ; nous pouvons les faire circuler Ă travers le code de nos applications. Pour afficher plusieurs Ă©lĂ©ments en React, nous utilisons un tableau dâĂ©lĂ©ments React.
En JavaScript, les tableaux ont une méthode map()
couramment utilisée pour transformer les données en un jeu de données dérivé, par exemple :
const numbers = [1, 2, 3];
const doubled = numbers.map(x => x * 2); // [2, 4, 6]
En utilisant la méthode map
, nous pouvons transformer notre historique de tours en Ă©lĂ©ments React reprĂ©sentant des boutons Ă lâĂ©cran, et afficher cette liste de boutons pour « revenir » Ă des tours passĂ©s.
Réalisons un map
sur notre history
dans la méthode render
de Game
:
render() {
const history = this.state.history;
const current = history[history.length - 1];
const winner = calculateWinner(current.squares);
const moves = history.map((step, move) => { const desc = move ? 'Revenir au tour n°' + move : 'Revenir au début de la partie'; return ( <li> <button onClick={() => this.jumpTo(move)}>{desc}</button> </li> ); });
let status;
if (winner) {
status = winner + ' a gagné';
} else {
status = 'Prochain joueur : ' + (this.state.xIsNext ? 'X' : 'O');
}
return (
<div className="game">
<div className="game-board">
<Board
squares={current.squares}
onClick={(i) => this.handleClick(i)}
/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol> </div>
</div>
);
}
Voir le code complet Ă ce stade
Pour chaque tour dans lâhistorique de notre partie de morpion, nous crĂ©ons un Ă©lĂ©ment de liste <li>
qui contient un <button>
. Le bouton a un gestionnaire onClick
qui appelle une méthode this.jumpTo()
. Nous nâavons pas encore implĂ©mentĂ© jumpTo()
. Pour le moment, nous devrions voir une liste des tours ayant déjà eu lieu pendant la partie, et un avertissement dans la console des outils de développement qui dit :
Warning: Each child in an array or iterator should have a unique âkeyâ prop. Check the render method of âGameâ.
(Avertissement : chaque enfant dâun tableau ou itĂ©rateur devrait avoir une prop âkeyâ unique. VĂ©rifiez la mĂ©thode render de âGameâ.)
Explorons ensemble ce que signifie cet avertissement.
Choisir une key
Quand on affiche une liste, React stocke des informations sur chaque élément de liste affiché. Lorsque nous mettons ensuite la liste à jour, React a besoin de déterminer quels éléments ont changé. Nous pourrions avoir ajouté, retiré, ré-ordonné ou mis à jour des éléments de la liste.
Imaginez que nous passions de ceci :
<li>Alice : 7 tĂąches restantes</li>
<li>Bob : 5 tĂąches restantes</li>
âŠĂ ceci :
<li>Bob : 9 tĂąches restantes</li>
<li>Claudia : 8 tĂąches restantes</li>
<li>Alice : 5 tĂąches restantes</li>
En plus des compteurs Ă jour, un humain qui lirait cette liste percevrait certainement que nous avons inversĂ© lâordre dâAlice et Bob, et ajoutĂ© Claudia entre eux. Mais React nâest quâun programme informatique, et nâa aucune idĂ©e de ce que nous voulions faire. Dans la mesure oĂč il ne peut pas deviner nos intentions, nous avons besoin de spĂ©cifier une prop key
pour chaque élément de liste, afin de les différencier les uns des autres. Une option consisterait à utiliser les chaßnes alice
, bob
et claudia
. Si nous affichions des donnĂ©es issues dâune base, Alice, Bob et Claudia auraient sĂ»rement des IDs que nous pourrions utiliser comme clĂ©s.
<li key={user.id}>{user.name} : {user.taskCount} tĂąches restantes</li>
Quand une liste est rĂ©-affichĂ©e, React prend la clĂ© de chaque Ă©lĂ©ment de la liste et recherche un Ă©lĂ©ment dans la liste prĂ©cĂ©dente dont la clĂ© correspondrait. Sâil sâagit dâune nouvelle clĂ©, React crĂ©e un composant. Si la nouvelle liste ne contient plus une clĂ© qui existait par le passĂ©, React dĂ©truit le composant devenu superflu. Si une correspondance est trouvĂ©e, le composant concernĂ© est dĂ©placĂ© (si besoin). Les clĂ©s permettent Ă React dâassocier une identitĂ© Ă chaque composant, ce qui lui permet de maintenir un Ă©tat entre deux affichages. Si la clĂ© dâun composant change, ce composant sera dĂ©truit et rĂ©-créé avec un nouvel Ă©tat.
La prop key
est spéciale et réservée en React (ainsi que ref
, une fonctionnalité plus avancée). Quand un élément est créé, React extrait sa prop key
et la stocke directement sur lâĂ©lĂ©ment renvoyĂ©. MĂȘme si key
semble appartenir aux props
, elle nâest pas rĂ©fĂ©rençable via this.props.key
. React lâutilise automatiquement pour dĂ©cider quels composants mettre Ă jour. Un composant nâa aucun moyen de connaĂźtre sa key
.
Nous vous recommandons fortement de spĂ©cifier des clĂ©s appropriĂ©es partout oĂč vous construisez des listes dynamiques. Si vous ne trouvez pas de clĂ© appropriĂ©e, câest peut-ĂȘtre lâoccasion de repenser la structure de vos donnĂ©es afin quâelle en fournisse une.
Si aucune clĂ© nâest spĂ©cifiĂ©e, React affichera un avertissement et utilisera par dĂ©faut lâindex de lâĂ©lĂ©ment dans le tableau. Mais recourir Ă lâindex est problĂ©matique lorsquâon essaie de rĂ©-ordonner, ajouter ou supprimer des Ă©lĂ©ments. Passer explicitement key={i}
Ă©vite lâavertissement, mais ne vous dispense pas de ces problĂšmes : nous conseillons dâĂ©viter cette approche dans la majoritĂ© des cas.
Les clĂ©s nâont pas besoin dâĂȘtre uniques au global ; elles ont juste besoin dâĂȘtre uniques au sein dâune liste donnĂ©e.
Implémenter le voyage dans le temps
Dans lâhistorique de la partie de morpion, chaque tour passĂ© a un ID unique qui lui est associĂ© : câest le numĂ©ro de sĂ©quence du tour. Comme les tours ne sont jamais rĂ©-ordonnĂ©s, retirĂ©s ou insĂ©rĂ©s (ailleurs quâĂ la fin), nous pouvons utiliser cet index comme clĂ© sans que ça pose problĂšme.
Dans la méthode render
du composant Game
, nous pouvons ajouter la clé avec <li key={move}>
, et lâavertissement de React au sujet des clĂ©s devrait disparaĂźtre :
const moves = history.map((step, move) => {
const desc = move ?
'Revenir au tour n°' + move :
'Revenir au début de la partie';
return (
<li key={move}> <button onClick={() => this.jumpTo(move)}>{desc}</button>
</li>
);
});
Voir le code complet Ă ce stade
Mais cliquer sur nâimporte quel bouton de cette liste lĂšve une erreur car la mĂ©thode jumpTo
nâest pas encore dĂ©finie. Avant de lâimplĂ©menter, nous allons ajouter stepNumber
Ă lâĂ©tat du composant Game
, pour indiquer quel tour est actuellement affiché.
Commençons par ajouter stepNumber: 0
Ă lâĂ©tat initial dans le constructor
de Game
:
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
history: [{
squares: Array(9).fill(null),
}],
stepNumber: 0, xIsNext: true,
};
}
Ensuite, définissons la méthode jumpTo
dans Game
pour quâelle mette Ă jour stepNumber
. Nous définirons aussi xIsNext
Ă true
si le numéro de tour que nous utilisons dans stepNumber
est pair :
handleClick(i) {
// Cette mĂ©thode nâa pas changĂ©
}
jumpTo(step) { this.setState({ stepNumber: step, xIsNext: (step % 2) === 0, }); }
render() {
// Cette mĂ©thode nâa pas changĂ©
}
Nous allons maintenant apporter quelques modifications à la méthode handleClick
de Game
, qui est déclenchée quand on clique sur une case.
LâĂ©tat stepNumber
que nous avons ajoutĂ© reflĂšte le tour actuellement affichĂ©. AprĂšs quâun nouveau tour est jouĂ©, nous devons mettre Ă jour stepNumber
en ajoutant stepNumber: history.length
au sein de lâargument de this.setState
. Ainsi, on est sûrs de ne pas rester bloqués sur le tour affiché aprÚs avoir choisi une case.
Nous devrons aussi remplacer la lecture de this.state.history
par this.state.history.slice(0, this.state.stepNumber + 1)
. Ainsi, nous sommes certains que si nous « revenons dans le passĂ© » puis jouons un nouveau tour Ă partir de ce point, nous retirerons de lâhistorique toute la partie « future » que ce nouveau coup invaliderait.
handleClick(i) {
const history = this.state.history.slice(0, this.state.stepNumber + 1); const current = history[history.length - 1];
const squares = current.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
history: history.concat([{
squares: squares
}]),
stepNumber: history.length, xIsNext: !this.state.xIsNext,
});
}
Pour finir, nous allons modifier la méthode render
du composant Game
pour quâelle nâaffiche plus systĂ©matiquement le dernier coup, mais plutĂŽt le tour indiquĂ© par stepNumber
:
render() {
const history = this.state.history;
const current = history[this.state.stepNumber]; const winner = calculateWinner(current.squares);
// Le reste nâa pas changĂ©
DĂ©sormais, si nous cliquons sur nâimporte quel tour dans lâhistorique de la partie, le plateau de morpion devrait immĂ©diatement se mettre Ă jour pour afficher lâĂ©tat du plateau suite Ă ce tour.
Voir le code complet Ă ce stade
Pour finir
Félicitations ! Vous avez créé un jeu de morpion qui :
- vous permet de jouer au morpion,
- indique quel joueur a remporté la partie,
- stocke un historique de jeu au fil des tours,
- permet aux joueurs de revenir Ă un point quelconque de lâhistorique.
Beau boulot ! Nous espérons que vous avez maintenant le sentiment de comprendre correctement comment fonctionne React.
Jetez un coup dâĆil au rĂ©sultat final ici : rĂ©sultat final.
Si vous avez encore du temps ou si vous souhaitez pratiquer vos nouvelles compĂ©tences React, voici quelques idĂ©es dâamĂ©liorations que vous pourriez apporter Ă ce jeu de morpion, listĂ©es par ordre croissant de difficultĂ© :
- Afficher lâemplacement de chaque coup dans lâhistorique de tours, au format
(colonne, ligne)
. - Mettre le tour affichĂ© en gras dans lâhistorique.
- Réécrire
Board
pour utiliser deux boucles afin dâafficher le plateau, plutĂŽt que de coder tout ça en dur. - Afficher un bouton de bascule qui permette de trier les tours par ordre chronologique, ou du plus rĂ©cent au plus ancien.
- Quand quelquâun gagne, mettre en exergue les trois cases qui ont permis la victoire.
- Quand personne ne gagne, afficher un message indiquant le match nul.
Au travers de ce tutoriel, nous avons touchĂ© Ă de nombreux concepts de React tels que les Ă©lĂ©ments, les composants, les props, et lâĂ©tat local. Pour de plus amples explications sur ces sujets, jetez un coup dâĆil au reste de la documentation. Pour en apprendre davantage sur la dĂ©finition de composants, vous pouvez consulter la rĂ©fĂ©rence de lâAPI React.Component
.