3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-06-13
[前回] Django+Reactで学ぶプログラミング基礎(21): Reactアプリのデバッグ

はじめに

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

今回の内容

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

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

現時点で、stateの課題

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

解決案

  • 案1

    • Boardコンポーネントが、各Squareコンポーネントに、現時点の stateを問い合わせる
      • デメリット
        • コードが分かりにくくなる
        • リファクタリングしづらい
  • 案2(ベスト解決策)

    • 親となるBoardコンポーネントが、各Squareの代わりにゲームの状態を保持
      • 子コンポーネント間で互いにやりとりが可能となる
    • Boardコンポーネントから、それぞれのSquarepropsを渡すことで、何を表示すべきか指示する
  • 子コンポーネントの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(空のマス目)を伝える
    • BoardrenderSquareは、コンストラクタで定義されたsquares配列の値を読み込む
    • それぞれのSquarevalueプロパティX/O/null(空のマス目)を受け取る
src/index.js
  renderSquare(i) {
    return <Square value={this.state.squares[i]} />;
  }

image.png

  • F5でデバッグ実行すると、ブラウザにReactアプリが表示される
    image.png

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

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

  • 現在、Boardがどのマス目に何が入っているか管理している

    • Squareがクリックされたら、Boardstateを更新する必要あり
  • stateは定義されているコンポーネント内でプライベート

    • よって、SquareからBoardstateを直接書き換えることはできない
  • 解決策: BoardからSquareに関数を渡す

    • マス目がクリックされたら、Squareがその関数を呼び出すように、renderSquareメソッドを修正
    • BoardからSquareprops2つ渡される
      • value
      • onClick
src/index.js
  renderSquare(i) {
    return (
      <Square
        value={this.state.squares[i]}
        onClick={() => this.handleClick(i)}
      />
    );
  }
  • Squareを以下のように修正
    • Squarerenderメソッド内のthis.state.valuethis.props.valueに書き換える
    • Squarerenderメソッド内のthis.setState()this.props.onClick()に書き換える
    • Squareはゲームの状態を管理しなくなったので、Squareconstructorを削除
src/index.js
class Square extends React.Component {
  render() {
    return (
      <button
        className="square"
        onClick={() => this.props.onClick()}
      >
        {this.props.value}
      </button>
    );
  }
}

image.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

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

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

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

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

おわりに

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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?