この記事の目的
公式チュートリアルで三目並べを作ってもらうのが一番理解できるが、その中でも特に日本語で説明しておいたほうがよさそうなポイントを纏めている。
本資料を読んだ後に、チュートリアルを両方やってもらったほうがより理解が深まる。
関連するQiita記事
Reactのざっくり概要
Reactコンポーネントとは
Reactコンポーネント間の値の受け渡し
参考資料
ドキュメント
チュートリアル
※余力があれば、以下のチュートリアルも行うことを推奨する
Getting Started with React - An Overview and Walkthrough Tutorial
Reactコンポーネントでstateをリフトアップ
stateを子コンポーネントに持たせる場合の問題点
Reactコンポーネント間の値の受け渡しでは、Squareコンポーネントのstateで値を保持していたが、このままだとBoardコンポーネントで9つあるSquareコンポーネントの値を取得しようとした場合に、9つのSquareコンポーネントにそれぞれ問い合わせをする必要がある。
そのようなコードもReactは許容するが、コードがわかりにくくなりバグを発生しやすく、リファクタリングも難しくなるので、Reactは**「stateのリフトアップ」**を推奨している。
stateのリフトアップとは?
各Squareコンポーネントで持っていたstateをBoardコンポーネントに移し、各Squareコンポーネントのpropsに対してstateの値を渡すようにする。
このように、SquareコンポーネントにあったstateをBoardコンポーネントに移すようなリファクタリングを、**「stateのリフトアップ」**と呼ぶ。
stateのリフトアップをするコード例
Squareコンポーネントのstateをリフトアップすると、以下のようなコードになる。
コードの例
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点。
- Boardコンポーネントにstateを持たせる
- Squareコンポーネントからstateを除去
- Boardコンポーネントのstateを、Squareコンポーネントのpropsに渡す
- Squareコンポーネントは、propsで受け取ったBoardコンポーネントのstateを表示する
子コンポーネントから親コンポーネントのstateを変更する例
Boardコンポーネントから見て、Squareコンポーネントは子コンポーネントになるので、BoardコンポーネントとSquareコンポーネントには親子関係があることになる。
Squareコンポーネント(子コンポーネント)からBoardコンポーネント(親コンポーネント)のstateを変更したい場合、子コンポーネントから親コンポーネントのstateを直接変更することはできない。
なぜなら、コンポーネントのstateはプライベートなフィールドなので外部から直接値を変更することはできないためである。
そのため、SquareコンポーネントからBoardコンポーネントのstateを変更する場合は、以下のように親コンポーネントから子コンポーネントにstateを変更する関数をpropsとして渡すことになる。
子コンポーネントでは、propsで受け取った親の関数を通して、親のstateを変更する。
コードの例
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点。
- 親コンポーネント(Boardコンポーネント)にstateを変更する処理(今回の場合は
handleClick
関数)を持たせる - 子コンポーネント(Squareコンポーネント)にstateを変更する処理(今回の場合は
handleClick
関数)をpropsとして渡す - 子コンポーネント(Squareコンポーネント)から親コンポーネント(Boardコンポーネント)のstateを変更するときは、propsで渡された親コンポーネント(Boardコンポーネント)の処理(今回の場合は
handleClick
関数)を呼び出す
stateのリフトアップを検討するときのポイント
以下の流れで実装とリファクタリングを区別し、リファクタリングでstateのリフトアップを行うのが好ましい。
-
実装
-
親子関係にあるコンポーネントであっても、子コンポーネントに”state”と”stateを変更する処理”がある状態で実装を進める
-
実装が完了し、テストで動作を確認する
-
リファクタリング
-
子コンポーネントにある”state”を、親コンポーネントにリフトアップする
-
子コンポーネントにある”stateを変更する処理”を、親コンポーネントにリフトアップする
-
子コンポーネントのpropsに、親コンポーネントの”state”と”stateを変更する処理”を渡す
-
子コンポーネントは、propsで渡された”state”と”stateを変更する処理”を使って、子コンポーネント側の変更を親コンポーネントのstateに格納する
-
リファクタリングが完了したら、テストで動作を確認する