Django+Reactで学ぶプログラミング基礎(27): Reactチュートリアル(タイムトラベル実装とチュートリアルまとめ)

##### [前回] [Django+Reactで学ぶプログラミング基礎(26): Reactチュートリアル(着手履歴表示とKeyプロパティ)](https://qiita.com/mingchun_zhao/items/55696ea89f9d05eccc13)

## はじめに

前回は、タイムトラベル機能に必要な知識を勉強しました。
今回は、その実装を完成させ、チュートリアルのまとめを行います。

## 今回の内容

- タイムトラベル機能を追加
    - タイムトラベルを実装
    - まとめ

## タイムトラベルを実装

### 三目並べゲームの着手履歴を表すリスト項目の`key`を決める

- すべての着手には、関連付けられた一意なIDが存在
    - 着手順のインデックスを表す連番数字
    - ゲーム中に、着手が`削除/挿入/並び替え`されることはない
        - よって、着手のインデックスを`key`として使うのは安全
    - `Game`コンポーネントの`render`メソッド内で、`key`を追加
        - `<li key={move}>`
```src/index.js
    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>
      );
    });
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/530b6ddb-8512-5da9-ba24-2972630da66d.png)

- VS Codeで、アプリをデバッグ起動(F5)
    - ブラウザでデベロッパーツール(F12)のコンソールを確認
        - Reactの`key`に関する警告が表示されなくなった
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/59664d2c-3f86-5d0e-cbf1-5b564463782f.png)

### リスト項目内のボタンクリックによりコールされる`jumpTo`メソッドを実装

- まず、`Game`コンポーネントの`state`に、いま何手目の状態を見ているかを表す`stepNumber`プロパティを追加
    - `Game`の`constructor`内で、`state`の初期値として`stepNumber: 0`を追加
```src/index.js
class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      history: [{
        squares: Array(9).fill(null),
      }],
      stepNumber: 0,
      xIsNext: true,
    };
  }
```

- 次に、`Game`内に`jumpTo`メソッドを定義
    - `stepNumber`を更新する
    - `stepNumber`の値が偶数の場合、`xIsNext`を`true`に設定
        - 偶数かの判断に、厳密等価演算子(`===`)を使用
            - オペランド比較時、型変換(キャスト)しない
            - 二つのオペランドの型が異なる場合、常に異なるものと判断
    - ※ `setState`で`state`の`history`プロパティは指定しなくてよい(変更不要のため)
        - 理由は、`state`の更新はマージされるため
            - Reactは`setState`で指定されたプロパティは更新、未指定のプロパティは`state`にそのまま残す
```src/index.js
  jumpTo(step) {
    this.setState({
      stepNumber: step,
      xIsNext: (step % 2) === 0,
    });
  }
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/5109d444-f59d-fbdd-a68f-36febe831258.png)

### マス目をクリックしたときに実行される`Game`コンポーネントの`handleClick`メソッドを変更

- `state`の`stepNumber`プロパティは、現在ユーザに見せている着手を表す
    - 新しい着手が発生した場合、`this.setState`の引数に`stepNumber: history.length`を加え、`stepNumber`を更新
- `時間を巻き戻し`過去の着手に戻った場合、それより新しい着手履歴を捨て去る
    - `this.state.history`を読み取る処理を、`this.state.history.slice(0, this.state.stepNumber + 1)`に書き換える
```src/index.js
  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,
    });
  }
```

- 最後に、常に最後の着手後状態をレンダリングするのではなく、`stepNumber`によって現在選択されている着手をレンダリング
    - ゲーム履歴内のいずれの手番をクリックしても、盤面は該当着手が発生した直後の状態を表示するようになる
    - `Game`コンポーネントの`render`を書き換える
```src/index.js
  render() {
    const history = this.state.history;
    const current = history[this.state.stepNumber];
    const winner = calculateWinner(current.squares);
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/e8ab18f1-9fd8-4ca8-ecb7-160a8d66cec4.png)

- ブラウザで更新されたアプリを確認
    - 5手目までプレイ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/6c97db34-bec9-ee51-f2cd-2c22cb7ad809.png)
    - `Go to move #2`をクリック
        - 盤面が2手目直後の状態表示に切り替わる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/f8db0084-464c-2cac-4276-3b1f2c6d6e21.png)

## まとめ

おめでとうございます! 三目並べゲーム完成です。

### チュートリアルで学んだ概念

- React要素
- Reactコンポーネント
- `Props`
- `State`
- `State` VS. `Props`
    - 共通点
        - Reactコンポーネントの以下操作で使用される
            - データの受け渡し
            - ハンドリング
            - 画面表示
    - 違い
        - `State`: コンポーネントが持っている状態
        - `Props`: 親コンポーネントから渡されたプロパティ
    - 使い分け
        - コンポーネント内部状態の操作には`State`を使用
        - 直接外部コンポーネントの`State`に接続する場合は、`Props`で受け渡す

### ゲーム機能のまとめ

- 三目並べが遊べる
- 決着がついたときに表示できる
- ゲーム進行にあわせ履歴が保存される
- 着手履歴の見直しや盤面の過去状態を参照できる

### 改良アイディアにチャレンジ

- 履歴内の着手の位置を`(col, row)`というフォーマットで表示
- 着手履歴のリスト中で現在選択されているアイテムを太字にする
- `Board`でマス目を並べる部分を、ハードコーディングでなく、2つのループを使用するように書き換える
- 着手履歴のリストを昇順/降順いずれでも並べかえられるよう、トグルボタン(toggle button)を追加
- どちらかが勝利した際に、勝利につながった3つのマス目をハイライト
- どちらも勝利しなかった場合、引き分けメッセージを表示

## おわりに

チュートリアルを通じて、Reactの基本を勉強しました。
次回も続きます。お楽しみに。

##### [次回] [Django+Reactで学ぶプログラミング基礎(28): Reduxチュートリアル(準備と概要)](https://qiita.com/mingchun_zhao/items/993ff45e5fed918aa8e3)