We want to hear from you!Take our 2021 Community Survey!
This site is no longer updated.Go to react.dev

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 :

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 :

  1. Assurez-vous de disposer d’une version installĂ©e de Node.js suffisamment rĂ©cente.
  2. Suivez les instructions d’installation de Create React App pour crĂ©er un nouveau projet.
npx create-react-app my-app
  1. 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 ..
  1. Ajoutez un fichier nommé index.css dans le dossier src/ et mettez-y ce code CSS.
  2. Ajoutez un fichier nommé index.js dans le dossier src/ et mettez-y ce code JS.
  3. Ajoutez les trois lignes suivantes tout en haut du index.js dans le dossier src/ :
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 :

React Devtools

AprĂšs : vous devriez voir un nombre dans chaque carrĂ© affichĂ©.

React Devtools

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 prop onClick. React ne l’appellera que suite Ă  un clic. Une erreur courante consiste Ă  oublier le () => de dĂ©part, pour Ă©crire seulement onClick={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 propre constructor 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 par this.state.value dans la balise <button>.
  • Remplacez le gestionnaire d’évĂ©nements onClick={...} par onClick={() => this.setState({value: 'X'})}.
  • Mettez les props className et onClick 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.

React Devtools

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 :

  1. Connectez-vous ou inscrivez-vous et confirmez votre adresse e-mail (obligatoire pour éviter le spam).
  2. Cliquez sur le bouton “Fork”.
  3. Cliquez sur “Change View” et choisissez “Debug mode”.
  4. 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 nulls, 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 renvoyant undefined, 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 par this.props.value dans la mĂ©thode render de Square
  • Remplacez this.setState() par this.props.onClick() dans la mĂ©thode render de Square
  • Supprimez le constructor de Square, 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 :

  1. La prop onClick du composant DOM natif <button> indique Ă  React de mettre en place un gestionnaire d’évĂ©nements pour les clics.
  2. Quand on cliquera sur le bouton, React appellera le gestionnaire d’évĂ©nements onClick dĂ©fini dans la mĂ©thode render() de Square.
  3. Ce gestionnaire d’évĂ©nements appelle this.props.onClick(). La prop onClick de Square a Ă©tĂ© spĂ©cifiĂ©e par le Board.
  4. Puisque le Board a passé onClick={() => this.handleClick(i)} à Square, ce dernier appelle en fait this.handleClick(i) (dans le contexte de Board) lors du clic.
  5. 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 que Square, vous avez toute latitude dans le nommage des props. On aurait pu nommer autrement la prop onClick de Square ou la mĂ©thode handleClick de Board, le code marcherait toujours. En React, une convention rĂ©pandue consiste Ă  utiliser des noms on[Event] pour les props qui reprĂ©sentent des Ă©vĂ©nements, et handle[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()} en onClick={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 de Board.
  • Remplacez this.state.squares[i] par this.props.squares[i] dans la mĂ©thode renderSquare de Board.
  • Remplacez this.handleClick(i) par this.props.onClick(i) dans la mĂ©thode renderSquare de Board.

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Ă©thode concat() 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Ă© :

  1. Afficher l’emplacement de chaque coup dans l’historique de tours, au format (colonne, ligne).
  2. Mettre le tour affichĂ© en gras dans l’historique.
  3. Réécrire Board pour utiliser deux boucles afin d’afficher le plateau, plutĂŽt que de coder tout ça en dur.
  4. Afficher un bouton de bascule qui permette de trier les tours par ordre chronologique, ou du plus récent au plus ancien.
  5. Quand quelqu’un gagne, mettre en exergue les trois cases qui ont permis la victoire.
  6. 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.

Avez-vous trouvĂ© cette page utile ?Modifier cette page