React公式チュートリアルのクラスコンポーネントを関数コンポーネントに書き替える

なかなか手をつけられなかったReact公式サイトのチュートリアルを、一通りやってみた。
ステートフックuseState()を使って、クラスコンポーネントから関数コンポーネントに書き換えてみたのでメモに残す。

公式サイトより：[チュートリアルReact の導入](https://ja.reactjs.org/tutorial/tutorial.html)
[三目並べ完成形](https://codepen.io/gaearon/pen/gWWZgR?editors=0010)

#### hookを呼び出す際に気をつけること => フックを呼び出すのはトップレベルのみ

> フックをループや条件分岐、あるいはネストされた関数内で呼び出してはいけません。代わりに、あなたの React の関数のトップレベルでのみ呼び出してください。これを守ることで、コンポーネントがレンダーされる際に毎回同じ順番で呼び出されるということが保証されます。これが、複数回 useState や useEffect が呼び出された場合でも React がフックの状態を正しく保持するための仕組みです。

参照: [React公式サイト フックのルール](https://ja.reactjs.org/docs/hooks-rules.html)

#### Square（三目並べの正方形のマス目）

```Square.jsx
import React from "react";

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

// Board（盤面） コンポーネントから { onClick, value } = props を受け取っている
const Square = ({ onClick, value }) => {
  return (
    <button className="square" onClick={onClick}>
      {value}
    </button>
  );
};

export default Square;
```

### Board（盤面）

```Board.jsx
import React from "react";
// Square（三目並べの正方形のマス目）コンポーネントをimport
import Square from "./Square";

// class Board extends React.Component {
//   renderSquare(i) {
//     return (
//       <Square
//         value={this.props.squares[i]}
//         onClick={() => this.props.onClick(i)}
//       />
//     );
//   }

// Game コンポーネントから { squares, onClick } = props を受け取っている
const Board = ({ squares, onClick }) => {
  const renderSquare = i => {
    return (
      <Square
        value={squares[i]}
        onClick={() => {
          onClick(i);
        }}
      />
    );
  };

  // render() {
  return (
    <>
      <div className="board-row">
        {renderSquare(0)}
        {renderSquare(1)}
        {renderSquare(2)}
      </div>
      <div className="board-row">
        {renderSquare(3)}
        {renderSquare(4)}
        {renderSquare(5)}
      </div>
      <div className="board-row">
        {renderSquare(6)}
        {renderSquare(7)}
        {renderSquare(8)}
      </div>
    </>
  );
  // }
};

export default Board;
```

### Game

Game(親)コンポーネントは props を使うことで子に情報を渡すことができた。こうすることで、子コンポーネントとの間で常に同期されるようになる。

```Game.jsx
// useStateをimport
import React, { useState } from "react";
// Board（盤面）コンポーネントをimport
import Board from "./Board";
import "./style.css";

// class Game extends React.Component {
//   constructor(props) {
//     super(props);
const Game = () => {
  //  クラスのconstructor内で `this.state` の初期化をやめて `useState` フックを使う
  //  this.state = {
  //    history: [
  //      {
  //        squares: Array(9).fill(null)
  //      }
  //   ],
  // useState()を使って、関数コンポーネントに状態を持たせる
  // const [state変数, set関数] = useState(初期値)
  const [history, setHistory] = useState([
    {
      squares: Array(9).fill(null)
    }
  ]);
  // stepNumber: 0,
  const [stepNumber, setStepNumber] = useState(0);
  // xIsNext: true
  const [xIsNext, setXIsNext] = useState(true);
  //     };
  //   }

  // handleClick(i) {
  const handleClick = i => {
    // const history = this.state.history.slice(0, this.state.stepNumber + 1);
    const historyCurrent = history.slice(0, stepNumber + 1);
    // const current = history[history.length - 1];
    const current = historyCurrent[historyCurrent.length - 1];
    // const squares = current.squares.slice();
    const squares = [...current.squares];

    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    // squares[i] = this.state.xIsNext ? "X" : "O";
    squares[i] = xIsNext ? "X" : "O";
    //this.setState({
    //  history: history.concat([
    //    {
    //      squares: squares
    //    }
    //  ]),

    setHistory([...historyCurrent, { squares }]);
    // stepNumber: history.length,
    setStepNumber(historyCurrent.length);
    // xIsNext: !this.state.xIsNext
    setXIsNext(!xIsNext);
  };
  //   });
  // }

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

  const jumpTo = step => {
    setStepNumber(step);
    setXIsNext(step % 2 === 0);
  };
  // render() {
  // const history = this.state.history;
  // const current = history[this.state.stepNumber];
  const current = history[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> */}
        <button onClick={() => jumpTo(move)}>{desc}</button>
      </li>
    );
  });

  let status;
  if (winner) {
    status = `Winner : ${winner}`;
  } else {
    // status = "Next player: " + (this.state.xIsNext ? "X" : "O");
    status = `Next Player : ${xIsNext ? "X" : "O"}`;
  }
  return (
    <div className="game">
      <div className="game-board">
        {/* <Board squares={current.squares} onClick={i => this.handleClick(i)} /> */}
        <Board squares={current.squares} onClick={i => handleClick(i)} />
      </div>
      <div className="game-info">
        <div>{status}</div>
        <ol>{moves}</ol>
      </div>
    </div>
  );
  // }
};

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

export default Game;
```

参考： [React公式サイト](https://ja.reactjs.org/)