React入門９: タイムトラベル(1)

# はじめに
私自身の復習兼、備忘録的な意味もあり、複数回の記事に渡って React を用いてマルバツゲーム(三目並べ)を開発していきたいと思います。
- [React](https://ja.react.dev/)
- [CodeSandbox(公式)](https://codesandbox.io/)
- [CodeSandbox(非公式)](https://csb-jp.github.io/)

## シリーズの一覧
1. [React入門１: 環境構築 [オンライン版]](https://qiita.com/banana_bancho/items/64fa6ccb58101eadbb63)
   - [React入門1.5: 環境構築 [ローカル版-番外編]](https://qiita.com/banana_bancho/private/30f5d9d2c5eea00ac11c)
1. [React入門２: 盤面の作成](https://qiita.com/banana_bancho/private/c50495b5d4c30555cec3)
1. [React入門３: インタラクションの実装](https://qiita.com/banana_bancho/private/557d14b2413cb0317ce4)
1. [React入門４: リファクタリング [リフトアップ編]](https://qiita.com/banana_bancho/private/81730326c004e52c6aad)
1. [React入門５: リファクタリング [インタラクション編]](https://qiita.com/banana_bancho/private/3f20d23aadb49fe233f8)
1. [React入門６: 手番の実装](https://qiita.com/banana_bancho/private/6bed838f67e575f58b72)
1. [React入門７: ゲームの勝利判定](https://qiita.com/banana_bancho/private/c0eca4e9348edb430561)
1. [React入門８: テキストの実装](https://qiita.com/banana_bancho/private/b841b1ce38482b31753c)
1. React入門９: タイムトラベル(1) (今回)
1. [React入門10: タイムトラベル(2)](https://qiita.com/banana_bancho/private/861398f1fa4efd378938)
1. [React入門11: タイムトラベル(3)](https://qiita.com/banana_bancho/private/0d3cf7e1b1ebbf20c5e0)

# 目的について
## 全体の目的
React公式のチュートリアルで公開されているマルバツゲームを 3x3 のマスで実装していきます。

https://ja.react.dev/learn/tutorial-tic-tac-toe

## 今回の目的
前回までの記事でゲームシステムを完成させました。今回の記事から、過去の手番に戻ることができるタイムトラベル機能を実装していきます。今回はその準備として、過去の盤面の状況を記憶するために、新たにコンポーネントを設計していきます。

# タイムトラベル(1)
次のページで、前回のソースファイルを確認できます。

https://codesandbox.io/p/sandbox/react-tutorial8-8vprcd

- 前回の内容はコチラから！

https://qiita.com/banana_bancho/items/b841b1ce38482b31753c

## 過去の手番を保持するアプローチ
現在、マルバツゲームは ```Board``` コンポーネントと ```Square``` コンポーネントで構成されています。

- ```Board``` コンポーネント
    - 盤面を表示する
    - ```state``` 型の ```squares``` 配列で盤面の状況を管理する
    - ```Square``` コンポーネントがクリックされたときのイベントとして ```handleClick()``` 関数を定義している
- ```Square``` コンポーネント
    - 盤面上にある1つのマスを表示する

そこで、手番毎の盤面の状況を保持する ```Game``` コンポーネントを作成していきます。このコンポーネントに、```Board``` コンポーネントの ```state``` 型の変数と配列をリフトアップします。

## コーディング
App.js にある ```Board``` コンポーネントを次のように変更します。

- メインコンポーネントとしての定義をやめる
- ```state``` 型の変数と配列の宣言を省く
- 次のプロパティを設定する
    - ```xIsNext``` : 現在の手番
    - ```squares``` : 現在の盤面の状況
    - ```onPlay```  : 盤面の状況を記憶する処理と手番を交代する処理を行う関数
- ```handleClick()``` 関数の変更
    - ```state``` 型の変数と配列の更新処理を省く
    - ```onPlay``` プロパティで受け取った関数を実行する
        - 更新された盤面の状況を渡す

```jsx:App.js の Board コンポーネント
function Board({ xIsNext, squares, onPlay }) {
  const winner = calculateWinner(squares);
  let status;

  if (winner) {
    status = "勝者: " + winner;
  } else {
    status = "プレイヤー: " + (xIsNext ? "X" : "O");
  }

  function handleClick(i) {
    if (squares[i]) {
      return;
    }

    const nextSquares = squares.slice();

    if (xIsNext) {
      nextSquares[i] = "X";
    } else {
      nextSquares[i] = "O";
    }
    onPlay(nextSquares);
  }

  return (
    <>
      <div>{status}</div>
      <div className="board-row">
        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
      </div>
    </>
  );
}
```

続いて、 ```Game``` コンポーネントを次のように定義します。
- これをメインコンポーネントとする
- ```Board``` コンポーネントのリフトアップする
    - ```state``` 型の ```history``` 配列を定義する
        - 盤面の状況を示す
        - 初期値: ```[Array(9).fill(null)] ```
        - set関数名: ```setHistory```
    - ```state``` 型の ```xIsNext``` 変数を定義する
        - 現在の着手を示す
        - 初期値: ```true```
        - set関数名: ```setXIsNext```
    - ```currentSquares``` 変数を定義する
        - 現在の盤面の状況を示す
        - 初期値: 最新の盤面の状況
- ```handlePlay()``` 関数の定義
    - 盤面の状況を記憶する
    - 手番を交代する
- ```Board``` コンポーネントをレンダーする
    - 各プロパティに適切な値または関数を渡す
```jsx:App.js の Game コンポーネント
export default function Game() {
  const [history, setHistory] = useState([Array(9).fill(null)]);
  const [xIsNext, setXIsNext] = useState(true);
  const currentSquares = history[history.length - 1];

  function handlePlay(nextSquares) {
    setHistory([...history, nextSquares]);
    setXIsNext(!xIsNext);
  }

  return (
    <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
  );
}
```

次に実行結果を示します。リフトアップをしたためコンポーネントの構成が変わりましたが、動作内容は前回と同じになります。

![動作結果.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2844961/2d6b9a44-2978-e7dd-545c-61aa930046f9.gif)

これで、盤面の状況は ```Game``` コンポーネントにある ```state``` 型の ```history``` 配列に記録できるようになりました。```history``` 配列の要素は、ある手番における盤面の状況を示します。具体的には、```history[i]``` という要素は ```i``` 回目の手番における盤面の状況、```history[i][0]``` は盤面の左上にあるマスの状態を示します。

# おわりに
今回は、タイムトラベルを実装するにあたり ```Game``` コンポーネントを定義したリフトアップをしていきました。次のページに現段階のソースファイルを示します。

https://codesandbox.io/p/sandbox/react-tutorial9-kj7rcq

次回は、```Game```コンポーネントにテキストを過去の手番に戻れるようにしていきます。

https://qiita.com/banana_bancho/private/861398f1fa4efd378938