はじめに
- Reactを使って画面作成をしてみたいと思うようになり、サイトを調べてチュートリアル を試しにやってみました。
- ここにかかれていることをサイト内にあるスターターコード を使って試したものになります。
- ○×ゲームを作ることが題材となっています。
- スターターコードにチュートリアルの内容に従いコードを書いていくことで動きを確認することはできました。
-
Reactのチュートリアルをやってみた① に投稿させていただいた第2弾になります。
- 前回は、
Props
とstate
について、理解した内容をイメージにしてみました。
- 前回は、
- 今回は、ゲーム完成までのコーディングを行うまでに学んだ以下の内容について纏めてみたいと思います。
- State のリフトアップ(親:Boardが子:Squareをコントロールできるようにする)
- 関数コンポーネント
- 番手の処理
- 勝ち負けの判定
前回のおさらい
-
Props
は、子のコンポーネントに対して引数を受け渡すのに利用される -
state
は、自コンポーネント内で情報を保持し記憶しておくことができる - 前回は、SquareとBoardのコンポーネントを準備し、Squareでイベントを検知して、情報を記憶しておき、Boardを再描画することで、Squareで記憶した値を表示できることを理解しました。イメージにするとこんな感じだと理解しました。
今回理解したこと
Stateのリフトアップ
今、行っているチュートリアルでは○×ゲームを完成させることにありますので、Square
コンポーネントで記憶している内容をBoard
コンポーネントで収集し、盤面の内容から勝ち負けを判定できるようにする必要があります。
そのため、以下のイメージにあるようなことをできるようにする必要があります。
そのためには、以下のイメージのように、親のコンポーネントでstate
を宣言して、Props
を使って子のコンポーネントに情報を展開することで、親コンポーネントで情報を管理し、子コンポーネント間も親を通して情報のやり取りをできるようにすることでStateのリフトアップを行います。
チュートリアルの○×ゲームでは、クリックのイベントも親コンポーネントで定義し、子コンポーネントの振る舞いを決めます。子コンポーネントは、クリックイベントが発生したら親コンポーネントが定義したイベントに従うようにしています。
コンポーネントのイメージと処理の流れをイメージしてみると、このような感じになると理解しました。
関数コンポーネント
Reactでは、rendaer
メソッドだけを有して、state
を持たないコンポーネントは、React.component
を継承する代わりに、Props
を引数として受け取り、表示すべき内容だけを返す(return)できるように関数としてていぎすることができる。
クラスコンポーネントとして書くより、関数コンポーネントはコーディングが楽になる。
実際、stateのリフトアップした結果を見てみると、実際にやっていることは、render
メソッドだけになっています。
番手の処理
番手の処理、つまり”○” と ”×” の攻め手の切り替えが👆の処理までだと実現できていないので、実現しましょう!という話になります。
ここは、state
で真偽(True/False)を記憶して、その評価の結果によって、”○”と”×”を切り替えられるようにしていくことで実現できます。
-
Board
コンポーネントのstate
に変数:xIsNext
をtrue
を初期地として追加 - イベントが発生するたびに真偽の変更と、"○"と"×"の切り替えができるように
handleClick
関数に処理を追加- squares[i]に変数:
xIsNext
の真偽によって、"○" か"×"をセット - setStateメソッドで、
squares[i]
をstate.squares
に、変数:xIsNext
に変数:xIsNext
ではない値をstate
の情報として記憶する
- squares[i]に変数:
-
Board
コンポーネントのレンダー内にconst
で定義されている今、どっちの番かを表示するstatus
にも変数:xIsNext
の真偽によって、"○" か"×"を切り替えができるように書き換える
勝ち負けの判定
勝ち負けの判定をするためのcomponentなどの描画などが目的の関数コンポートンとは異なるヘルパー関数を用意するとあった。
ここでは、処理のイメージ化して書いてきたけど、純粋にロジックなのでコードベースで理解をした。
コードは以下のようにfunction
で関数であることを宣言して、関数名(引数)としてreturn
で処理結果を返している。
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
一方の呼び出し側のBoard
コンポートでは、以下のように書き換えることで、勝ち負けの判定の結果に基づき、const
で定義していたstatus
をlet
(再代入可能な変数に定義しなおしている)のstatus
に対して、ゲームが続行で、手番がどっちか、もしくは決着がついて、Winnerが誰になったのか、文字列を再代入する形で表現できるようにしている。
render() {
const winner = calculateWinner(this.state.squares);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
まとめ
準備するコンポーネントが 'state' を定義し情報を記憶させておくべきコンポーネントか否かでコンポーネントクラスで定義するのか、関数コンポーネントとして定義するのかを整理しながら、まずは動かすためにコンポーネントクラスで定義したとしても、やりたいことを整理していく中で リファクタリング しながら実装することがポイントになると理解した。