Help us understand the problem. What is going on with this article?

Reactコンポーネントでstateをリフトアップ

この記事の目的

公式チュートリアルで三目並べを作ってもらうのが一番理解できるが、その中でも特に日本語で説明しておいたほうがよさそうなポイントを纏めている。
本資料を読んだ後に、チュートリアルを両方やってもらったほうがより理解が深まる。

関連するQiita記事

Reactのざっくり概要
Reactコンポーネントとは
Reactコンポーネント間の値の受け渡し

参考資料

ドキュメント

公式ドキュメント

チュートリアル

公式チュートリアル

※余力があれば、以下のチュートリアルも行うことを推奨する
Getting Started with React - An Overview and Walkthrough Tutorial

Reactコンポーネントでstateをリフトアップ

stateを子コンポーネントに持たせる場合の問題点

Reactコンポーネント間の値の受け渡しでは、Squareコンポーネントのstateで値を保持していたが、このままだとBoardコンポーネントで9つあるSquareコンポーネントの値を取得しようとした場合に、9つのSquareコンポーネントにそれぞれ問い合わせをする必要がある。

image.png

そのようなコードもReactは許容するが、コードがわかりにくくなりバグを発生しやすく、リファクタリングも難しくなるので、Reactは「stateのリフトアップ」を推奨している。

stateのリフトアップとは?

各Squareコンポーネントで持っていたstateをBoardコンポーネントに移し、各Squareコンポーネントのpropsに対してstateの値を渡すようにする。

image.png

このように、SquareコンポーネントにあったstateをBoardコンポーネントに移すようなリファクタリングを、「stateのリフトアップ」と呼ぶ。

stateのリフトアップをするコード例

Squareコンポーネントのstateをリフトアップすると、以下のようなコードになる。

コードの例

board.js
class Square extends React.Component {  //ポイント2
  render() {
    return (
      <button
        className="square"
        onClick={() => this.props.onClick()} 
      >
        {this.props.value}  //ポイント4
      </button>
    );
  }
}

class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = {  //ポイント1
      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]}  //ポイント3
        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>
    );
  }
}

注目すべきポイント

stateのリフトアップを見る上でのポイントは以下の4点。

  1. Boardコンポーネントにstateを持たせる
  2. Squareコンポーネントからstateを除去
  3. Boardコンポーネントのstateを、Squareコンポーネントのpropsに渡す
  4. Squareコンポーネントは、propsで受け取ったBoardコンポーネントのstateを表示する

子コンポーネントから親コンポーネントのstateを変更する例

Boardコンポーネントから見て、Squareコンポーネントは子コンポーネントになるので、BoardコンポーネントとSquareコンポーネントには親子関係があることになる。

Squareコンポーネント(子コンポーネント)からBoardコンポーネント(親コンポーネント)のstateを変更したい場合、子コンポーネントから親コンポーネントのstateを直接変更することはできない。

なぜなら、コンポーネントのstateはプライベートなフィールドなので外部から直接値を変更することはできないためである。

そのため、SquareコンポーネントからBoardコンポーネントのstateを変更する場合は、以下のように親コンポーネントから子コンポーネントにstateを変更する関数をpropsとして渡すことになる。
子コンポーネントでは、propsで受け取った親の関数を通して、親のstateを変更する。

コードの例

board.js
class Square extends React.Component {
  render() {
    return (
      <button
        className="square"
        onClick={() => this.props.onClick()}  //ポイント3
      >
        {this.props.value}
      </button>
    );
  }
}

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

  handleClick(i) {  //ポイント1
    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)}  //ポイント2
      />
    );
  }

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

注目すべきポイント

子コンポーネントから親コンポーネントのstateを変更する上でのポイントは以下の3点。

  1. 親コンポーネント(Boardコンポーネント)にstateを変更する処理(今回の場合はhandleClick関数)を持たせる
  2. 子コンポーネント(Squareコンポーネント)にstateを変更する処理(今回の場合はhandleClick関数)をpropsとして渡す
  3. 子コンポーネント(Squareコンポーネント)から親コンポーネント(Boardコンポーネント)のstateを変更するときは、propsで渡された親コンポーネント(Boardコンポーネント)の処理(今回の場合はhandleClick関数)を呼び出す

stateのリフトアップを検討するときのポイント

以下の流れで実装とリファクタリングを区別し、リファクタリングでstateのリフトアップを行うのが好ましい。

  1. 実装

    1. 親子関係にあるコンポーネントであっても、子コンポーネントに”state”と”stateを変更する処理”がある状態で実装を進める
    2. 実装が完了し、テストで動作を確認する
  2. リファクタリング

    1. 子コンポーネントにある”state”を、親コンポーネントにリフトアップする
    2. 子コンポーネントにある”stateを変更する処理”を、親コンポーネントにリフトアップする
    3. 子コンポーネントのpropsに、親コンポーネントの”state”と”stateを変更する処理”を渡す
    4. 子コンポーネントは、propsで渡された”state”と”stateを変更する処理”を使って、子コンポーネント側の変更を親コンポーネントのstateに格納する
    5. リファクタリングが完了したら、テストで動作を確認する
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした