【React】チュートリアル その2
今回はチュートリアルで作成していた「三目並べ」の続き。
「タイムトラベル機能の追加 」を実装したので、その振り返りです。
チュートリアルで作成できる三目並べは下記リンクから実際に遊べます。
https://codepen.io/gaearon/pen/gWWZgR?editors=0010
【目次】
1. Game基盤クラス
三目並べの盤面やゲームの進行状況を管理している、メインとなるクラス。
class Game extends React.Component { ・・・ }
1.1. コンストラクタ【constructor】
constructor(props) { super(props); this.state = { history : [{ squares: Array(9).fill(null), }], stepNumber: 0, xIsNext: true, } }
Gameコンポーネントの初期stateを3つ、コンストラクタ内でセットしています。
【history】
「nullで初期化されたサイズが9の配列squares」の配列。
三目並べの手番の履歴を管理する。
【stepNumber】
現在、何手目の状態なのかを管理する。
【xIsNext】
プレーヤーの手番を管理する。
1.2. マス目ボタンのクリックイベント【handleClick】
1.2.1. 最新の盤面情報を取得
handleClick(i) { const history = this.state.history.slice(0, this.state.stepNumber + 1); const current = history[history.length - 1]; const squares = current.squares.slice(); ・・・ }
【history】
history stateの配列の先頭から、「stepNumber state + 1」までの要素を取得。
例えば、stepNumber stateが "3"の場合、history state配列の1番目(インデックス "0")から、4(=3+1)番目(インデックス "3")までの要素を取得する。
【current】
上記で取得したhistoryから、最新の盤面情報を取得。
【squares】
現在の盤面の状態の各マス目を取得。
1.2.2. 盤面の情報を更新するか判定
・・・ if (calculateWinner(squares) || squares[i]) { return; } ・・・ }
勝敗がすでについている、もしくはクリックしたマスが既に埋まっている場合は何もせずにreturnする。
1.2.3. stateを更新
・・・ squares[i] = this.state.xIsNext ? 'X' : 'O'; this.setState({ history: history.concat([{squares: squares, }]), stepNumber: history.length, xIsNext: !this.state.xIsNext, }); }
最新のマス目情報「squares」のクリックされたマス目に、手番により"X" or "O"を代入。
「setState()」でコンポーネントのstateを更新し、画面を再描画する。
1.3. 過去の手番に戻る【jumpTo】
jumpTo(step) { this.setState({ stepNumber: step, xIsNext: (step % 2) === 0, }); }
引数の「step」番目の手番まで、コンポーネントのstateを更新&画面の再描画を行う。
1.4. レンダー関数【render】
stateが更新されるたびにrender関数が呼ばれ、画面が再描画されます。
1.4.1. 現在の手番や勝敗を取得
render() { const history = this.state.history; const current = history[this.state.stepNumber]; const winner = calculateWinner(current.squares); ・・・ }
【history】
history stateを取得しています。
【current 】
上記で取得した「this.state.history」のうち、現在の盤面情報を取得しています。
【winner 】
前回説明した「calculateWinner」関数を呼び出し、勝敗を取得します。
1.4.2. 過去の手番に戻ることのできるボタンを生成
render() { ・・・ const moves = history.map((step, move) => { const desc = move ? 'Go to move #' + move : 'Go to game start'; return ( <li key={move}> <button onClick={() => this.jumpTo(move)}> {desc} </button> </li> ); }); ・・・ }
moves は、過去の手番に戻る(ジャンプする)ことのできるボタン一覧を描画するためのReact要素。
mapメソッドを使ってhistory配列の各要素(手番)について、
* ボタンに表示させる文字列React要素「desc 」を生成
* リスト要素でボタンを1行描画するJSXを「move」へ返している。
「jumpTo()」は後程説明します。
【Reactのリストについて】
Reactでリストを描画する際は「key」を割り当てる必要があります。
Reactはリストが変更された場合、どこが変更されたのかを知る必要があります。
(仮想DOMにより差分のみを更新するため)
そのため、リストの各項目を区別できるように、keyプロパティを割り当ててあげます。
リストが再描画されるときは、Reactはリストの各項目のkeyについて、前回(再描画前)のリスト項目内に同じkeyを持つものがないか探します。
前回リストに存在しないKeyが含まれていた場合
⇒コンポーネントを作成前回のリストにあったkeyが新しいリストに存在しない場合
⇒以前のコンポーネントを破棄前回リストのkeyとマッチするものが存在した場合
⇒対応するコンポーネントを移動
1.4.3. 現在の状況を判定
render() { ・・・ let status; if (winner) { status = 'Winner: ' + winner; } else { status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); } ・・・ }
勝負がついているのか、ついていない場合は現在どちらの手番なのかを変数「status」に文字列として保持します。
1.4.4. JSXをreturnする
render() { ・・・ return ( <div className="game"> <div className="game-board"> <Board squares = {current.squares} onClick = {(i) => this.handleClick(i)} /> </div> <div className="game-info"> <div>{status}</div> <ol>{moves}</ol> </div> </div> ); }
「className="game-board"」のdiv要素では、Boardコンポーネントを描画します。
Boardコンポーネントには、現在の盤面の状況と、盤面のマス目ボタンが押されたときに呼び出されるメソッドをpropsとして渡しています。
「className="game-info"」div要素では、
「1.4.3. 現在の状況を判定」で作成した文字列変数「status」と、
「1.4.2. 過去の手番に戻ることのできるボタンを生成」で生成したReact要素「move」のJSXを、「ol」タグで囲むことで項番付きリストとしてボタンを描画しています。
2. 盤面クラス
三目並べの盤面を描画するクラス。
class Board extends React.Component { ・・・ }
2.1. マス目描画【renderSquare】
renderSquare(i) { return ( <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)} /> ); }
「Square」関数コンポーネントをreturnしています。
「Square」関数コンポーネントへは、Gameコンポーネントから渡されたprops内の最新の盤面情報のi番目のマス目と、onClickメソッド(GameコンポーネントのhandleClick()メソッド)をpropsとして渡しています。
2.2. レンダー関数【render】
render() { return ( <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> ); }
盤面の各マス目を3×3になるように「renderSquare(i)」で描画しています。
3. マス目ボタンの関数コンポーネント
function Square(props) { return ( <button className="square" onClick={props.onClick}> {props.value} </button> ); }
引数はBoardコンポーネントから渡されたpropsです。
盤面のマス目をボタンとして描画するためのJSXをreturnしています。
マス目ボタンがクリックされたときには、onClickイベントに指定されている「GameコンポーネントのhandleClickメソッド」が呼び出されます。
4. まとめ
React公式のチュートリアルを一通り実施し、そのコードを備忘録的に解説しました。
自分の認識間違いなどあるかもしれませんが、まだ初心者のためご容赦ください(-_-メ)