Django+Reactで学ぶプログラミング基礎(26): Reactチュートリアル(着手履歴表示とKeyプロパティ)

##### [前回] [Django+Reactで学ぶプログラミング基礎(25): Reactチュートリアル(タイムトラベル機能)](https://qiita.com/mingchun_zhao/items/c70be5e14623759548af)

## はじめに

前回は、タイムトラベル機能を追加しました。
今回は、その続きです。

## 今回の内容

- タイムトラベル機能を追加
    - 過去の着手を表示
    - Keyを選ぶ

## 過去の着手を表示

### React要素は第一級JavaScriptオブジェクトで、アプリ内で受け渡し可能

- 第一級オブジェクトの性質
    - 変数やデータ構造に格納できる
    - 関数のパラメータ(引数)として渡せる
    - 関数の戻り値として返却可能
    - プロパティやメソッドを持つことができる
    - 無名関数(無名リテラル)として表現できる
    - 実行時に動的生成できる

- Reactで複数要素を描画するには、React要素の配列を使う
    - JavaScript配列の`map()`メソッドを用いて、データを別のデータにマッピング可能
```
const numbers = [1, 2, 3];
const doubled = numbers.map(x => x * 2); // [2, 4, 6]
```

### 着手履歴の配列をマッピングし、画面上のボタンを表すReact要素を作成

- 過去の手番に`ジャンプ`するためのボタン一覧を表示
- `Game`の`render`メソッド内で`history`に`map`を適用
    - `history`配列をループ処理する部分で
        - `step`変数は、`history`内の現在の要素を表す
        - `move`変数は、現在要素のインデックスを表す
```src/index.js
  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>
      );
    });

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

### 着手履歴に該当するボタン`<button>`を持つリストアイテム`<li>`を作成

- ボタンには`onClick`ハンドラが存在
    - `this.jumpTo()`というメソッドを呼び出す(実装は後で)
    - これにより、ゲーム内で行われた着手のリストが表示される

- VS Codeでアプリをデバッグ起動(F5)し、ブラウザで確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/efeec0b8-aa6f-8535-2f20-0ceb60d05efa.png)


## `key`を選ぶ

- ブラウザで、デベロッパーツール(F12で起動)のコンソールに出力される警告に着目
    - `Warning: Each child in an array or iterator should have a unique “key” prop. Check the render method of “Game”.`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/a0f8859e-047f-3176-2c07-f710450a3675.png)

- `key`は配列またはイテレータの特別なプロパティで、Reactによって予約される
    - Reactが、どの要素が変更/追加/削除されたかを識別する際、自動的に使用される
    - 配列内の項目を正しく識別するため、それぞれの項目に`key`を与えるべき
    - `key`を`this.props.key`のような形では、参照できない
    - コンポーネントが自身の`key`を確認する方法はない

- 動的リストを構築する場合、正しい`key`を割り当てることが強く推奨される
    - 適切な`key`がない場合、データ構造を再構成し、`key`を付与すべき

- `key`が指定されなかった場合、Reactは警告を表示する
    - デフォルトの`key`として、配列のインデックスが使用される
    - 配列のインデックスを`key`として用いることは、トラブルの元
        - 項目の並び替え/挿入/削除などの操作で問題が発生する
    - 明示的に`key={i}`を渡すことで、警告を消すことはできるが
        - 配列のインデックスを使う場合と同様、問題が生じるため推奨されない

- `key`はグローバルに一意である必要はない
    - コンポーネントとその兄弟間で一意であれば十分

- リストをレンダリングする際、各リスト項目に`key`情報を持たせるべき
    - リストが変更された場合、Reactがどのアイテムが変更されたか把握できる
    - リストアイテムに対する、追加/削除/並び替え/中身書き換え、などの操作で
        - リストの各項目に`key`プロパティを与えることで、兄弟要素と区別できる
```
<li key={user.id}>{user.name}: {user.taskCount} tasks left</li>
```

- `key`を用いた、リストの再レンダリング
    - Reactは新リスト項目の`key`に対し、旧リスト項目内に同じ`key`を持つものが存在しないか探す
        - 旧リストに存在しなかった`key`が、新リストに含まれている場合
            - Reactはコンポーネントを新規作成
        - 旧リストに存在したいずれの`key`も、新リストに含まれていない場合
            - Reactは以前のコンポーネントを破棄
        - 2つの`key`がマッチした場合
            - 該当コンポーネントは移動される

- `key`は、Reactにコンポーネントの同一性に関する情報を与える
    - よって、Reactが再レンダリングされても、`state`を保持できる
    - コンポーネントの`key`が変わった場合
        - コンポーネントは破棄され、新しい`state`で再作成される

## おわりに

着手履歴の画面表示と`Key`プロパティを勉強しました。
次回も続きます。お楽しみに。

##### [次回] [Django+Reactで学ぶプログラミング基礎(27): Reactチュートリアル(タイムトラベル実装とチュートリアルまとめ)](https://qiita.com/mingchun_zhao/items/626685221299476890a4)