React: A JavaScript library for building user interfaces

De Wikijoan
Dreceres ràpides: navegació, cerca

Contingut

Referències

Un altre tutorial:

Lectures i videos previs

En la part de teoria de la UF2 teniu un recull d'informació sobre frameworks de Javascript. És interessant tenir una visió general de quins són els frameworks més populars, i quines són les tendències.

Instal·lació

Get Started:

Installation > Create a New React App

Recommended Toolchains If you're learning React or creating a new single-page app, use Create React App.

You'll need to have Node >= 8.10 and npm >= 5.6 on your machine. To create a project, run:

$ node --version
v8.10.0

$ npm --version
3.5.2 (professor)

(Professor): intento actualitzar npm però no hi ha manera. De totes maneres, no passa res. La versió de npm > 5.6 és perquè inclou npx, que es pot instal·lar manualment.

NOTA: Per actualitzar npm el millor és actualitzar Node. Per actualitzar farem servir NVM: Node Version Manager

$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash
$ nvm install v12.16.1
...
Note: you may need to close & re-open your terminal window for nvm command to be available.

$ node --version
v12.16.1

$ npm --version
6.13.4

npx ve amb npm 5.2+. Com que tinc la versió 3.5.2, ho instal·lo manualment:

$ sudo npm install npx (amb la versió correcta de ''npm'' no caldrà instal·lar ''npx'')

Anem a crear doncs, la nostra primera aplicació:

$ cd /home/joan/M06_WEC_1920/UF2 -> aquí posaràs, evidentment, la teva ruta
$ PS1="$ " (amaga la ruta a Linux)
$ sudo chown -R 1000:1000 /home/joan/.npm (a tú segurament aquesta comanda no et caldrà si tens la versió correcta de npm)
npx create-react-app my-app
...
Success! Created my-app at /home/joan/M06_WEC_1920/UF2/my-app
Inside that directory, you can run several commands:

  npm start
    Starts the development server.

  npm run build
    Bundles the app into static files for production.

  npm test
    Starts the test runner.

  npm run eject
    Removes this tool and copies build dependencies, configuration files
    and scripts into the app directory. If you do this, you can’t go back!

We suggest that you begin by typing:

  cd my-app
  npm start

Així doncs ja podem arrencar la nostra aplicació:

$ cd my-app
$ npm start
  ...
Compiled successfully!

Ara en el navegador ja podem veure l'aplicació inicial per defecte:

Mentre estigui arrencada l'aplicació, quan fem canvis en els fitxers, el navegador es refrescarà automàticament.

Note that the development build is not optimized.

To create a production build, use npm run build.

Per finalitzar la instal·lació, hauries de configurar el teu IDE per tal de què ressalti correctament el codi JSX (combinació de Javascript i HTML). Pots seguir les instruccions de:

La majoria dels alumnes utilitzen Visual Studio Code. Algú (com el professor), utilitza Sublime Text 3.

En el Sublime 3, instal·lem el paquet Babel, i aleshores View > Syntax > Babel > Javascript (Babel)

Tutorial oficial: Tic-Tac-Toe (3 en ratlla)

És el tutorial oficial:

In this tutorial, we'll show how to build an interactive tic-tac-toe game with React.

The tutorial is divided into several sections:

Setup for the Tutorial

Nosaltres farem l'opció 2: Setup Option 2: Local Development Environment

$ npx create-react-app my-app

aquesta és l'aplicació que havíem fet en la 1a instal·lació.

Delete all files in the src/ folder of the new project (no esborrar la carpeta src/, només els fitxers que hi ha a dins)

$ cd my-app
$ cd src

# If you're using a Mac or Linux:
$ rm -f *

# Or, if you're on Windows:
# del *

# Then, switch back to the project folder
$ cd .. -> tornem a estar en el directori my-app

Add a file named index.css in the src/ folder with this CSS code (veure el tutorial).

Add a file named index.js in the src/ folder with this JS code (veure el tutorial).

Add these three lines to the top of index.js in the src/ folder:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';

Si no teníem el servidor arrencat, ara ho tornem a fer:

$ npm start

ja podem veure el nostre taulell del 3 en ratlla.

Overview

Ja tenim una aplicació funcionant que encara no fa res. Ara és el moment d'entendre una mica què és el que estem fent (però a aquestes altures ja hauries d'haver vist els videos i articles previs).

NOTA: És important que llegeixis el tutorial original, perquè aquí a la wiki no posarem tota la informació. La wiki pretén ser una guia d'orientació per als alumnes aclarint els punts que poden ser més problemàtics, però no és un copy-paste de l'article original.

Què és React?

React is a declarative, efficient, and flexible JavaScript library for building user interfaces. It lets you compose complex UIs from small and isolated pieces of code called "components".

React has a few different kinds of components, but we'll start with React.Component subclasses:

class ShoppingList extends React.Component {
  render() {
    return (
      <div className="shopping-list">
        <h1>Shopping List for {this.props.name}</h1>
        <ul>
          <li>Instagram</li>
          <li>WhatsApp</li>
          <li>Oculus</li>
        </ul>
      </div>
    );
  }
}

// Example usage: <ShoppingList name="Mark" />

Si és la primera vegada que veus el codi, el més sorprenent és veure com es barreja el codi Javascript i HTML sense cap problema: sintaxi JSX. En temps de compilació (build), el codi es tradueix a un codi pur Javascript tipus:

return React.createElement('div', {className: 'shopping-list'},
  React.createElement('h1', /* ... h1 children ... */),
  React.createElement('ul', /* ... ul children ... */)
);

Inspecting the starter code

Ens centrem en el codi index.js. Veiem que hi ha tres components React:

Aquí ja veiem com reutilitzem els components, doncs veiem que el taulell 3x3 està format per 9 botons utilitzant el component Square. D'altra banda, veiem que el component Square el que fa és renderitzar un botó html (button).

Passing Data Through Props

Fés els següents canvis a index.js:

class Board extends React.Component {
  renderSquare(i) {
    return <Square value={i} />;
  }
}
class Square extends React.Component {
  render() {
    return (
      <button className="square">
        {this.props.value}
      </button>
    );
  }
}

Com a resultat, hauràs de veure com els botons ja tenen un número. Recorda que no cal refrescar el navegador (tens l'aplicació en funcionament amb npm start).

El que hem fet és passar una prop (propietat) des del component Board (pare) al component Square (fill). Passar props és la manera com flueix la informació en les aplicacions React, des dels pares als fills.

Making an Interactive Component

En aquest apartat el que farem serà posar totes les caselles en blanc, i programar l'event de què quan fem click sobre una casella, posarem una X.

El compmponent Square quedarà de la següent manera:

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>
    );
  }
}

La idea és que tenim la possibilitat de guardar variables en forma d' estats. Per exemple, tenim l'estat value associat al valor que ha de tenir la casella. Com veiem en el codi, podem programar l'event onClick que canvia l'estat a value=Xper a cada botó.

Fixem-nos que estem programant el comportament dels 9 botons, i la variable que guarda el seu valor, sense bucles ni matrius ni res.

Developer Tools

En el navegador que utilitzis (Chrome o Firefox), has d'instal·lar l'extensió React Developer Tools per tal de poder inspeccionar allò que estàs fent, i detectar els errors. Per a Chrome:

Un cop instal·lada l'extensió hauràs de reiniciar el navegador. Si l'aplicació està feta amb React, com és el nostre cas, eEn el Chrome, sota les Eines de Desenvolupament, podràs trobar les seccions Components i Profiler. Podràs visualitzar en forma d'arbre tota l'estructura de Components (i podràs visualitzar totes les props).

Completing the Game

Coses que falten: haurem d'alternar entre X i O; i haurem de determinar el guanyador.

Lifting State Up (Pujar cap amunt l'estat)

En aquests moments, cada Square manté el valor del seu state, i això no ens val per saber qui guanya. Hem de pujar el valor del Square al seu pare, el Board, de manera que el Board sàpiga el valor de tots els Squares.

La millor manera de fer-ho és emmagatzemar l'estat del joc en el component Board pare en comptes d'emmagatzemar-lo en cada Square. El component Board pot dir-li a cada Square què ha de visualitzar, passant-li una prop (igual que vam fer al principi quan li passàvem el número que s'havia de veure en el Square).

Això ho fem mitjançant shared states. Per recollir dades des dels múltiples fills, o perquè dos components fills es comuniquin entre ells, hem de declarar un shared state (estat compartit) en el component pare. El component pare passarà el valor de l'estat als fills mitjançant props. D'aquesta manera els components fills estaran sincronitzats entre ells i amb el component pare.

Al final d'aquest punt els components Square i Board queden de la següent manera:

class Square extends React.Component {
  render() {
    return (
      <button
        className="square"
        onClick={() => this.props.onClick()}
      >
        {this.props.value}
      </button>
    );
  }
}


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 = 'Next player: 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>
    );
  }
}

Aquesta és l'explicació del codi:

Quan cliquem sobre Square, es crida la funció onClick que proporciona el Board. Això s'aconsegueix de la següent manera:

  1. La prop onClick del built-in DOM <button> component li diu a React que implementi un click event listener.
  2. Quan es clica el botó, React crida l'event onClick handler que està definit en el mètode render() del Square.
  3. Aquest event handler crida a this.props.onClick(). Però aqusta prop onClick del Square s'ha especificat en el Board.
  4. El Board li passa al Square la manera com ha de reaccionar en el onClick:
    onClick={() => this.handleClick(i)}
    i per tant el Square crida this.handleClick(i) quan es clica.
  5. En el Board definim el mètode handleClick(), que el que fa és agafar el valor actual dels Squares, i canviar en el quadre que li toca el seu valor per una X, i tornar a guardar l'estat:
  handleClick(i) {
    const squares = this.state.squares.slice();
    squares[i] = 'X';
    this.setState({squares: squares});
  }

Quan l'estat del Board canvia, els components Square en re-renderitzen automàticament. El que hem fet és guardar l'estat de tots els Squares en el Board, i això ens permetrà més endavant saber qui ha guanyat.

Els components Square ja no mantenen l'estat: reben valors del seu pare, i informen al seu pare de quan els cliquen. Per tant, els components Square són components controlats. El Board té control total sobre els fills.

Notar que a handleClick fem servir .slice() per crear una còpia de l'array de squares en comptes de modificar l'array directament (veiem per què en la propera secció).

Why Immutability Is Important

There are generally two approaches to changing data. The first approach is to mutate the data by directly changing the data's values. The second approach is to replace the data with a new copy which has the desired changes.

Data Change with Mutation:

var player = {score: 1, name: 'Jeff'};
player.score = 2;
// Now player is {score: 2, name: 'Jeff'}

Data Change without Mutation

var player = {score: 1, name: 'Jeff'};

var newPlayer = Object.assign({}, player, {score: 2});
// Now player is unchanged, but newPlayer is {score: 2, name: 'Jeff'}

// Or if you are using object spread syntax proposal, you can write:
// var newPlayer = {...player, score: 2};

The end result is the same but by not mutating (or changing the underlying data) directly, we gain several benefits described below.

  1. Complex Features Become Simple
  2. Detecting Changes
  3. Determining When to Re-Render in React

(TBD)

Function Components

Ara canviarem el component classe Square per una funció component.

A React, les funcions component són una manera més senzilla de construir components que només contenen un mètode render i no han de guardar el seu propi estat. En comptes de definir una classe que faci extends de React.Component, podem escriure una funció que agafi les props com a input i retorni allò que s'ha de renderitzar. Els components function són menys tediosos d'escriure que les classes, i molts components es poden escriure d'aquesta manera.

Reemplacem:

class Square extends React.Component {
  render() {
    return (
      <button
        className="square"
        onClick={() => this.props.onClick()}
      >
        {this.props.value}
      </button>
    );
  }
}

per

function Square(props) {
  return (
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  );
}

Taking Turns

Ara hem d'anar altenant entre jugadors: es comença per X i el següent jugador marca O.

Necessitem un nou estat per al Board:

xIsNext: true,

per saber si toca una X o una O.

Hem d'actualitzar també el handleClick:

  handleClick(i) {
    const squares = this.state.squares.slice();
    squares[i] = this.state.xIsNext ? 'X' : 'O';
    this.setState({
      squares: squares,
      xIsNext: !this.state.xIsNext,
    });
  }

I també la informació del text que es mostrarà per pantalla, en el render() del Board:

  render() {
    const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');

    return (
      // the rest has not changed

El component Board queda de la següent manera:

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 = 'Next player: ' + (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>
    );
  }
}

Declaring a Winner

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;
}

I hem de cridar aquesta funció cada cop que renderitzem el Board:

render() {
    const winner = calculateWinner(this.state.squares);
    let status;
    if (winner) {
      status = 'Winner: ' + winner;
    } else {
      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
    }

    return (
      // the rest has not changed

I també un petit canvi a handleClick() de manera que si ja tenim guanyador, no es poden fer més moviments:

  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,
    });
  }

Fins aquí ja tenim el codi funcionant i podem jugar una partida. En la secció següent s'implementa una millora.

Adding Time Travel

Storing a History of Moves

Es tracta de guardar l'historial dels moviments, per tal de poder tornar endarrere.

Si anem modificant l'array de squares aleshores no ho podrem fer. Però en canvi, si anem guardant còpies de l'array de squares, aleshores serà més fàcil. És per això que després de cada moviment s'ha utilitzat slice(), per crear una còpia de l'array.

Es crea un nou array anomenat history, i contindrà l'historial dels moviments. Per exemple, després de dos moviments:

history = [
  // Before first move
  {
    squares: [
      null, null, null,
      null, null, null,
      null, null, null,
    ]
  },
  // After first move
  {
    squares: [
      null, null, null,
      null, 'X', null,
      null, null, null,
    ]
  },
  // After second move
  {
    squares: [
      null, null, null,
      null, 'X', null,
      null, null, 'O',
    ]
  },
  // ...
]

Lifting State Up, Again

I qui s'encarrega de mantenir l'estat de l'array history? Doncs en aquest cas serà el Game, que és el pare del Board. Té lògica que sigui així.

Volem que el top-level Game component mostri la llista dels moviments passats. Haurà de tenir accés a history, així doncs situarem l'estat history en el top-level Game component.

La classe Game queda de moment de la següent manera:

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>
    );
  }
}

Next, we'll have the Board component receive squares and onClick props from the Game component. Since we now have a single click handler in Board for many Squares, we'll need to pass the location of each Square into the onClick handler to indicate which Square was clicked. Here are the required steps to transform the Board component:

Hem de fer encara més canvis en els components Board i Game.

Hem d'actualitzar la funció render del component Game per tal de mostrar el valor actual del history i determinar i mostrar el status actual del game.

I com que ara és el Game el que renderitza el game status, podem treure el codi equivalent del render del component Board, doncs ara ja no cal.

I encara hem de moure el mètode handleClick des del component Board al component Game. I hem de modificar handleClick perquè l'estat del component Game funciona diferent. Hem de concatenar la nova history al darrere de l'array de history (fem servir concat en comptes de push perquè no modifica l'array original).

En aquest punt, l'estat del joc i el handleClick resideixen en el component Game. En el component Board només hi ha el render del taulell i el renderSquare.

Showing the Past Moves

Volem mostrar tota la llista de moviments.

En JavaScript els arrays tenen el mètode map() que serveix per mapejar dades a altres dades, per exemple, podem crear una matriu amb valors dobles de la matriu original (sense haver de fer cap bucle):

const numbers = [1, 2, 3];
const doubled = numbers.map(x => x * 2); // [2, 4, 6]

I farem servir aquesta tècnica per representar els butons de les diferents jugades de la partida.

Mètode 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 ?
        'Go to move #' + move :
        'Go to game start';
      return (
        <li>
          <button onClick={() => this.jumpTo(move)}>{desc}</button>
        </li>
      );
    });
...

Per cada moviment del joc hem creat un element de la lista li que conté un button. Aquest button té associat un onClick que crida a this.jumpTo(), que encara no hem programat.

Recordar que la manera com fem el mapeig seria equivalent a:

    const moves = history.map(function(step, move) {...
    });

Aquesta part és confusa, sens dubte. Recordem que history està format per una llista d'objectes, que són un array de 9 elements. step són aquests objectes que anem recorrent i que anem mapejant segons la funció donada. Pot ser d'ajuda posar un parell d'alerts:

    const moves = history.map((step, move) => {
      alert(step)
      alert(move)

Si mirem la referència de la funció map, veiem que la funció de callback agafa tres arguments: currentValue (que seria el step); index (que és el move)

En aquest punt pots veure una llista de botons que es van actualitzant a mida que fem moviments, però aquests botons provoquen un error.

Picking a Key

El problema està en que la nostra llista li no té claus (keys) per cadascun dels elements. El cocepte de key és molt important a React. Els arrays tenen index, però el concepte d'index és diferent del de clau (imaginem que volem ordenar l'array, l'index no ens serveix).

Passar key={i} silencia el warning que hem obtingut, però no soluciona el problema perquè els index d'array no són recomanats.

Implementing Time Travel

En el cas del tic-tac-toe, els botons per accedir als diferents moviments no s'han de reordenar, i per tant en aquest cas és segur fer servir l'index de l'array com a clau (key).

En el render del component Game veurem una cosa com:

        <li key={move}>
          <button onClick={() => this.jumpTo(move)}>{desc}</button>
        </li>

Encara ens falta programar el mètode jumpTo(). Però abans afegirem a l'estat del Game la variable stepNumber. En el constructor del Game:

    this.state = {
      history: [{
        squares: Array(9).fill(null),
      }],
      stepNumber: 0,
      xIsNext: true,
    };

I en el component Game:

  jumpTo(step) {
    this.setState({
      stepNumber: step,
      xIsNext: (step % 2) === 0,
    });
  }

Ja falta poquet. Ara hem de programar el handleClick del Game per dir com s'ha de reaccionar davant del click dels botons.

  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,
    });
  }

Finalment, modificarem el render del component Game de manera que sempre renderitzi el moviment que hem seleccionat d'acord amb el stepNumber:

  render() {
    const history = this.state.history;
    const current = history[this.state.stepNumber];
    const winner = calculateWinner(current.squares);

    // the rest has not changed

Fixem-nos que si clico endarrere en el temps i faig un nou moviment, l'historial dels altres moviments ja no són vàlids i desapareixen de la llista com ha de ser.

Wrapping Up

Hem arribat al final del tutorial i podem jugar al tres en ratlla. Si tens temps i ganes, es proposen fer les següents millores al codi:

  1. Mostrar la localització de cada moviment amb el format (col, row) en la llista del history de moviments.
  2. Posar en negreta l'element seleccionat de la llista.
  3. Reescriure el component Board de manera que en comptes de què la taula estigui hardcoded (3 div's), fer dpos bucles aniuats.
  4. Posar un botó toggle per ordenar la llista de moviments en ordre ascendent o descendent.
  5. Quan algú guanyi, highlight els tres quadrats que han fet guanyar la partida.
  6. Quan algú guanyi, mostrar el missatge de què han fet taules.

Codi final

El codi final per al script index.js és el següent:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';

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;
}

function Square(props) {
  return (
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  );
}

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() {
    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>
    );
  }
}

class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      history: [{
        squares: Array(9).fill(null),
      }],
      stepNumber: 0,
      xIsNext: true,
    };
  }

  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,
    });
  }

  jumpTo(step) {
    this.setState({
      stepNumber: step,
      xIsNext: (step % 2) === 0,
    });
  }

  render() {
    const history = this.state.history;
    const current = history[this.state.stepNumber];
    const winner = calculateWinner(current.squares);

    const moves = history.map((step, move) => {
      const desc = move ?
        'Go to move #' + move :
        'Go to game start';
      return (
        <li key={move}>
          <button onClick={() => this.jumpTo(move)}>{desc}</button>
        </li>
      );
    });

    let status;
    if (winner) {
      status = 'Winner: ' + winner;
    } else {
      status = 'Next player: ' + (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>
    );
  }
}

// ========================================

ReactDOM.render(
  <Game />,
  document.getElementById('root')
);

Codi extra: modificacions proposades (descarregar codi)

Tic-tac-toe-extra.png

Al final del tutorial es proposen fer les següents millores:

  1. Display the location for each move in the format (col, row) in the move history list.
  2. Bold the currently selected item in the move list.
  3. Rewrite Board to use two loops to make the squares instead of hardcoding them.
  4. Add a toggle button that lets you sort the moves in either ascending or descending order.
  5. When someone wins, highlight the three squares that caused the win.
  6. When no one wins, display a message about the result being a draw.

S'han realitzat tots els canvis proposats (amb diferent nivell de dificultat).

Aquí tens el codi:

S'ha desplegat aquesta aplicació a Heroku:

Canviar el punt d'entrada de l'aplicació: index.js per index_extra.js

Vull mantenir les dues versions de l'aplicatiu. La primera versió:

La segona versió:

La pregunta és, com fer que quan fem npm start vagi a cercar el script index_extra.js.

Una possibilitat és modificar el fitxer de configuració:

i cercar les línies que comencen per appIndexJs (n'hi ha tres). En aquestes línies, canviar src/index per src/index_extra.

No sé si és la manera més ràpida o millor de fer-ho, però funciona.

Evidentment, si es treballa amb versions (git i branques) no cal fer aquestes coses.

Production build

To create a production build, use npm run build:

$ npm run build

> my-app@0.1.0 build /home/joan/M06_WEC_1920/UF2/my-app
> react-scripts build

Creating an optimized production build...
Compiled successfully.

File sizes after gzip:

  40.22 KB  build/static/js/2.1a0425fb.chunk.js
  1019 B    build/static/js/main.4d39ff09.chunk.js
  771 B     build/static/js/runtime-main.e1daef6b.js
  371 B     build/static/css/main.3fa7586b.chunk.css

The project was built assuming it is hosted at /.
You can control this with the homepage field in your package.json.

The build folder is ready to be deployed.
You may serve it with a static server:

  npm install -g serve
  serve -s build

Find out more about deployment here:

  bit.ly/CRA-deploy

S'ha creat la carpeta build/

$ npm install -g serve

protesta per tema de permisos. Com es comenta, primer he de fer: $ sudo chown -R 1000:1000 "/home/joan/.npm" </pre> I ara ja podem instal·lar el servidor, que només haurem de fer-ho una vegada:

$ npm install -g serve

I ara ja podem arrencar el servidor:

$ serve -s build

Per defecte, serveix l'aplicació pel port 5000:



creat per Joan Quintana Compte, març-abril 2020

Eines de l'usuari
Espais de noms
Variants
Accions
Navegació
Institut Jaume Balmes
Màquines recreatives
CNC
Informàtica musical
joanillo.org Planet
Eines