Django+Reactで学ぶプログラミング基礎(22): Reactチュートリアル(stateを親コンポーネントで管理)

##### [前回] [Django+Reactで学ぶプログラミング基礎(21): Reactアプリのデバッグ](https://qiita.com/mingchun_zhao/items/4c690fc5071ddb2b9da1)

## はじめに

前回は、Reactアプリのデバッグ方法を勉強しました。
今回は、チュートリアルに戻り、ゲームを完成させます。

## 今回の内容

- ゲームを完成させる
    - 子コンポーネントの`state`を親コンポーネントに移譲
    - 子コンポーネントを親コンポーネントの制御下に置く

## 子コンポーネントの`state`を親コンポーネントに移譲

### 現時点で、`state`の課題

- それぞれの`Square`コンポーネントがゲームの状態を保持している
- どちらが勝利したかチェックするためには、9個のマス目の値を1カ所で管理する必要あり

### 解決案
- 案1
    - `Board`コンポーネントが、各`Square`コンポーネントに、現時点の `state`を問い合わせる
        - デメリット
            - コードが分かりにくくなる
            - リファクタリングしづらい
- 案2(ベスト解決策)
    - 親となる`Board`コンポーネントが、各`Square`の代わりにゲームの状態を保持
        - 子コンポーネント間で互いにやりとりが可能となる
    - `Board`コンポーネントから、それぞれの`Square`に`props`を渡すことで、何を表示すべきか指示する

- 子コンポーネントの`state`を親コンポーネントに移譲するメリット
    - 複数の子コンポーネントからデータを集めて管理可能
    - 子コンポーネント同士、または親との間で常に同期が可能

### コード修正

- `Board`にコンストラクタを追加
    - 初期`state`として、9個のマス目に対応する9個の`null`値をセット
    ```
    [
      null, null, null,
      null, null, null,
      null, null, null,
    ]
    ```
    - 後で盤面が埋まっていくと、`this.state.squares`配列は以下のようになる
    ```
    [
      'O', null, 'X',
      'X', 'X', 'O',
      'O', null, null,
    ]
    ```

```src/index.js
class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(null),
    };
  }

  renderSquare(i) {
    return <Square value={i} />;
  }
```

- `props`を渡すメカニズムを実装
    - `Board`を書き換え、それぞれの`Square`に現在の値`X`/`O`/`null(空のマス目)`を伝える
    - `Board`の`renderSquare`は、コンストラクタで定義された`squares`配列の値を読み込む
    - それぞれの`Square`が`value`プロパティ`X`/`O`/`null`(空のマス目)を受け取る
```src/index.js
  renderSquare(i) {
    return <Square value={this.state.squares[i]} />;
  }
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/bc90f693-e804-bca3-79af-29b96823aaf9.png)

- F5でデバッグ実行すると、ブラウザにReactアプリが表示される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/2a21645d-61c2-3471-f898-93568a47d961.png)

## 子コンポーネントを親コンポーネントの制御下に置く

### マス目がクリックされた時の挙動を変更

- 現在、`Board`がどのマス目に何が入っているか管理している
    - `Square`がクリックされたら、`Board`の`state`を更新する必要あり

- `state`は定義されているコンポーネント内でプライベート
    - よって、`Square`から`Board`の`state`を直接書き換えることはできない

- 解決策: `Board`から`Square`に関数を渡す
    - マス目がクリックされたら、`Square`がその関数を呼び出すように、`renderSquare`メソッドを修正
    - `Board`から`Square`に`props`2つ渡される
        - `value`
        - `onClick`
```src/index.js
  renderSquare(i) {
    return (
      <Square
        value={this.state.squares[i]}
        onClick={() => this.handleClick(i)}
      />
    );
  }
```

- `Square`を以下のように修正
    - `Square`の`render`メソッド内の`this.state.value`を `this.props.value`に書き換える
    - `Square`の`render`メソッド内の`this.setState()`を `this.props.onClick()`に書き換える
    - `Square`はゲームの状態を管理しなくなったので、`Square`の`constructor`を削除
```src/index.js
class Square extends React.Component {
  render() {
    return (
      <button
        className="square"
        onClick={() => this.props.onClick()}
      >
        {this.props.value}
      </button>
    );
  }
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/66943a53-9862-2f21-1f0c-261c4f5f6667.png)

- `Board`クラスに`handleClick`を定義
    - マス目をクリックしたら、`handleClick`が呼び出されるように
```src/index.js
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>
    );
  }
}
```

- F5を押してデバッグ起動
    - マス目をクリックすると値が書き込まれるようになる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/23c8f020-7d03-e393-6c4b-078eada84cd2.png)

### これで、`Board`コンポーネントが`Square`コンポーネントを制御するようになった

- ゲーム状態は個々の`Square`コンポーネントではなく、`Board`コンポーネント内に保存される
- `Board`の`state`が変更されると
    - 個々の`Square`コンポーネントも自動的に再レンダリングされる
- 全てのマス目の状態を`Board`コンポーネント内で保持することで
    - どちらが勝者か判定できるようになる

### `Square`をクリックすると、`Board`から渡された`onClick`関数がコールされるメカニズム

- 組み込みDOMコンポーネント`<button>`に`onClick`プロパティを設定
    - Reactは、ここでクリックに対するイベントリスナーを設定
- `<button>`がクリックされると
    - Reactは、`Square`の`render()`メソッド内に定義されている`onClick`イベントハンドラ(アロー関数)をコール
- イベントハンドラが`this.props.onClick()`をコール
    - `Square`の`onClick`プロパティは、`Board`から渡されたもの
- `Board`が`Square`に渡した`onClick`プロパティは
    - `onClick={() => this.handleClick(i)}`
        - よって、`Square`をクリックしたら、`Board`の`handleClick(i)`が呼び出される

## おわりに

子コンポーネントの`state`管理を親コンポーネントに委ねました。
そして、子コンポーネントは親により制御されるようになりました。
次回も続きます。お楽しみに。

##### [次回] [Django+Reactで学ぶプログラミング基礎(23): Reactチュートリアル(イミュータビリティ/関数コンポーネント)](https://qiita.com/mingchun_zhao/items/e974f0f6a4d34aaa18e2)