こちらのReact公式チュートリアルをやってみて、要点をまとめました!
データを渡す
BoardコンポーネントからSquareコンポーネントにデータを渡す。
propsとしてvalueという名前の値をSquareに渡すときの、渡す側、受け取り側のそれぞれの記述は以下のようになります。
渡す側
class Board extends React.Component {
renderSquare(i) {
return <Square value={i}/>;
}
Squareコンポーネントにvalueを渡す。
受け取り側
Squareのrenderメソッドで、渡された値が表示されるよう{this.props.value}と指定
class Square extends React.Component {
render() {
return (
<button className="square">
{this.props.value}
</button>
);
}
}
これで、BoardコンポーネントからSquareコンポーネントにpropsを渡すことができました。
クリックイベントを作成する
<button className="square" onClick={function() {console.log('click');}}>
これによりSquareをクリックすると、アラートclickが表示される。
これをアロー関数で書き換えると
<button className="square" onClick={() => {console.log('click');}}>
onClick={console.log(‘click’);}とすると何がいけないのか?
クリックされたときに関数を実行させたい。この記述だと、再レンダリングされる度によびだされてしまう。
state
stateを使ってコンポーネントが何かを覚えさせる。
コンストラクタでthis.stateとすることで、状態を持つことができる。
現在のsquareの状態をthis.stateに保存して、クリックされたときにそれを変更するようにする。
①そのためにまずはコンストラクタを追加
constructor(props) {
super(props);
this.state = {
value: null,
};
}
コンストラクタを定義する際は常にsuperを呼ぶ必要がある。
そのため、constructrでは全てsuper(props)の呼び出しから始まる。
②Squareのrenderメソッドを書き換えて、クリック時にstateの値が表示されるようにする
- this.propsをthis.stateに書き換え
- onClickイベントを変更
alert表示から、Xという値の表示に変更する。
onClick={() => {alert('click');}}
↓
onClick={() => this.setState({value: 'X'})}
stateのリフトアップ
現時点では、それぞれのSquareコンポーネントが、ゲームの状態を保持している。
9個のマス目の値を一箇所で管理したい。
そのためには親のBoardコンポーネントで保持する。
このように複数の子要素からデータを集める場合は、親コンポーネント内で共有のstateを宣言し、親コンポーネントはpropsを使うことで子に情報を返す。
これを、stateを親コンポーネントにリフトアップするという。
①初期stateとして9個のマス全てにnullをセットする。
class Board extends React.Component {
constructor(props);
super(props);
this.state = {
squares: Array(9).fill(null),
};
}
renderSquare(i) {
return <Square value={i} />;
}
②Boardコンストラクタ内で定義した配列squaresを読み込む
renderSquare(i) {
return <Square value={this.state.squares[i]} />;
}
どのマスに何が入っているのかを管理しているのはBoardだが、
SquareがBoardのstateを更新できるようにしたい。
しかし、SquareからBoardのstateを直接書き換えることはできない。
そのため、BoardからSquareに関数を渡し、クリック時にSquareにその関数を読んでもらうようにする。
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
);
}
③Squareに以下変更を加える。
- this.stateをthis.propsに変更
- this.setStateをthis.props.onClickに変更
- ゲームの状態を管理していた以下のconstructorが不要のため削除
constructor(props) {
super(props);
this.state = {
value: null,
};
}
class Square extends React.Component {
render() {
return (
<button className="square"
onClick={() => this.props.onClick()}
>
{this.props.value}
</button>
);
}
}
上記コードは何をしているか?
- buttonへのonClickプロパティの設定により、イベントリスナを設定する
イベントリスナ:イベント発生で動作するようにした関数のこと - ボタンクリックで、onClickのイベントハンドラがコールされる
イベントハンドラ:特定のイベントに反応して特定の処理を行うもの - これがthis.props.onClick()をコールする
④handleClickを定義する
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = 'X';
this.setState({squaews: squares});
}
これでどうなったか?
状態が個々の Square コンポーネントではなく Board コンポーネント内に保存されるようになった。
Board の state が変更になると、個々の Square コンポーネントも自動的に再レンダーされる。
Board が Square コンポーネントを全面的に制御している状態になった。
.slice()というメソッドを使ってsquare配列のコピーを作ることができる。
このように、変化するデータの扱いには2種類のアプローチがある。
- データの値を直接いじってミューテートする(ミュータブル)
- 新しいデータのコピーで古いデータを書き換える(イミュータブル)
ミューテートしないことによるメリット
- 以前のヒストリを保って再利用することができることで、実装が楽になる
- 中身が直接書き換えられ、変更があったかどうかの検出が困難といったことがなくなる
- 変更があったかが簡単にわかることによりコンポーネントをいつ再レンダーすべきなのか決定しやすくなる
関数コンポーネントへの書き換え
関数コンポーネントとは、自分のstateを持たないコンポーネントをよりシンプルに書く方法
以下のclassによる書き方を書き換える
class Square extends React.Component {
render() {
return (
<button className="square" onClick={() => this.props.onClick()}>
{this.props.value}
</button>
);
}
}
関数コンポーネント では以下のように書くことができる
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
クリックでXとOを切り替えられるようにする
目標物
- どちらのプレーヤの手番なのかをxIsNextで決める
- xIsNext: trueとすることで、先手を”X”にする
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
xIsNext: true,
};
}
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
});
}
Next playerの表示も切り替わるようにする。
render() {
const status = 'Next player: ' +(this.state.xIsNext ? 'X' : 'O');
タイムトラベル機能の追加
やりたいこと
トップレベルの Game コンポーネント内で過去の着手の履歴を表示したい
方法
Game コンポーネントが history
にアクセスできる必要があるので、history
という state を Game コンポーネントに置く。
それにより、squares
の state を、子である Board コンポーネントから取り除くことができる。
BoardにあるstateをGameコンポーネントにリフトアップすることにより Game コンポーネントは Board のデータを完全に制御でき、history
内の過去のデータを Board にレンダーさせることができる。
まとめると、、、
Game(親)
history(state)をここに設置。
Board(子)
historyがGameにあるので、squaresのstateが不要
と、ここで挫折。。。
チュートリアルを最後まで完遂出来ず、一旦他の方法で学習します。
コメント