[前回] Django+Reactで学ぶプログラミング基礎(21): Reactアプリのデバッグ
はじめに
前回は、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]} />;
}
子コンポーネントを親コンポーネントの制御下に置く
マス目がクリックされた時の挙動を変更
-
現在、
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>
);
}
}
-
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>
);
}
}
これで、Board
コンポーネントがSquare
コンポーネントを制御するようになった
- ゲーム状態は個々の
Square
コンポーネントではなく、Board
コンポーネント内に保存される -
Board
のstate
が変更されると- 個々の
Square
コンポーネントも自動的に再レンダリングされる
- 個々の
- 全てのマス目の状態を
Board
コンポーネント内で保持することで- どちらが勝者か判定できるようになる
Square
をクリックすると、Board
から渡されたonClick
関数がコールされるメカニズム
- 組み込みDOMコンポーネント
<button>
にonClick
プロパティを設定- Reactは、ここでクリックに対するイベントリスナーを設定
-
<button>
がクリックされると- Reactは、
Square
のrender()
メソッド内に定義されているonClick
イベントハンドラ(アロー関数)をコール
- Reactは、
- イベントハンドラが
this.props.onClick()
をコール-
Square
のonClick
プロパティは、Board
から渡されたもの
-
-
Board
がSquare
に渡したonClick
プロパティは-
onClick={() => this.handleClick(i)}
- よって、
Square
をクリックしたら、Board
のhandleClick(i)
が呼び出される
- よって、
-
おわりに
子コンポーネントのstate
管理を親コンポーネントに委ねました。
そして、子コンポーネントは親により制御されるようになりました。
次回も続きます。お楽しみに。