はじめに
私自身の復習兼、備忘録的な意味もあり、複数回の記事に渡って React を用いてマルバツゲーム(三目並べ)を開発していきたいと思います。
シリーズの一覧
- React入門1: 環境構築 [オンライン版]
- React入門2: 盤面の作成
- React入門3: インタラクションの実装
- React入門4: リファクタリング [リフトアップ編]
- React入門5: リファクタリング [インタラクション編]
- React入門6: 手番の実装
- React入門7: ゲームの勝利判定
- React入門8: テキストの実装
- React入門9: タイムトラベル(1) (今回)
- React入門10: タイムトラベル(2)
- React入門11: タイムトラベル(3)
目的について
全体の目的
React公式のチュートリアルで公開されているマルバツゲームを 3x3 のマスで実装していきます。
今回の目的
前回までの記事でゲームシステムを完成させました。今回の記事から、過去の手番に戻ることができるタイムトラベル機能を実装していきます。今回はその準備として、過去の盤面の状況を記憶するために、新たにコンポーネントを設計していきます。
タイムトラベル(1)
次のページで、前回のソースファイルを確認できます。
- 前回の内容はコチラから!
過去の手番を保持するアプローチ
現在、マルバツゲームは Board
コンポーネントと Square
コンポーネントで構成されています。
-
Board
コンポーネント- 盤面を表示する
-
state
型のsquares
配列で盤面の状況を管理する -
Square
コンポーネントがクリックされたときのイベントとしてhandleClick()
関数を定義している
-
Square
コンポーネント- 盤面上にある1つのマスを表示する
そこで、手番毎の盤面の状況を保持する Game
コンポーネントを作成していきます。このコンポーネントに、Board
コンポーネントの state
型の変数と配列をリフトアップします。
コーディング
App.js にある Board
コンポーネントを次のように変更します。
- メインコンポーネントとしての定義をやめる
-
state
型の変数と配列の宣言を省く - 次のプロパティを設定する
-
xIsNext
: 現在の手番 -
squares
: 現在の盤面の状況 -
onPlay
: 盤面の状況を記憶する処理と手番を交代する処理を行う関数
-
-
handleClick()
関数の変更-
state
型の変数と配列の更新処理を省く -
onPlay
プロパティで受け取った関数を実行する- 更新された盤面の状況を渡す
-
function Board({ xIsNext, squares, onPlay }) {
const winner = calculateWinner(squares);
let status;
if (winner) {
status = "勝者: " + winner;
} else {
status = "プレイヤー: " + (xIsNext ? "X" : "O");
}
function handleClick(i) {
if (squares[i]) {
return;
}
const nextSquares = squares.slice();
if (xIsNext) {
nextSquares[i] = "X";
} else {
nextSquares[i] = "O";
}
onPlay(nextSquares);
}
return (
<>
<div>{status}</div>
<div className="board-row">
<Square value={squares[0]} onSquareClick={() => handleClick(0)} />
<Square value={squares[1]} onSquareClick={() => handleClick(1)} />
<Square value={squares[2]} onSquareClick={() => handleClick(2)} />
</div>
<div className="board-row">
<Square value={squares[3]} onSquareClick={() => handleClick(3)} />
<Square value={squares[4]} onSquareClick={() => handleClick(4)} />
<Square value={squares[5]} onSquareClick={() => handleClick(5)} />
</div>
<div className="board-row">
<Square value={squares[6]} onSquareClick={() => handleClick(6)} />
<Square value={squares[7]} onSquareClick={() => handleClick(7)} />
<Square value={squares[8]} onSquareClick={() => handleClick(8)} />
</div>
</>
);
}
続いて、 Game
コンポーネントを次のように定義します。
- これをメインコンポーネントとする
-
Board
コンポーネントのリフトアップする-
state
型のhistory
配列を定義する- 盤面の状況を示す
- 初期値:
[Array(9).fill(null)]
- set関数名:
setHistory
-
state
型のxIsNext
変数を定義する- 現在の着手を示す
- 初期値:
true
- set関数名:
setXIsNext
-
currentSquares
変数を定義する- 現在の盤面の状況を示す
- 初期値: 最新の盤面の状況
-
-
handlePlay()
関数の定義- 盤面の状況を記憶する
- 手番を交代する
-
Board
コンポーネントをレンダーする- 各プロパティに適切な値または関数を渡す
export default function Game() {
const [history, setHistory] = useState([Array(9).fill(null)]);
const [xIsNext, setXIsNext] = useState(true);
const currentSquares = history[history.length - 1];
function handlePlay(nextSquares) {
setHistory([...history, nextSquares]);
setXIsNext(!xIsNext);
}
return (
<Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
);
}
次に実行結果を示します。リフトアップをしたためコンポーネントの構成が変わりましたが、動作内容は前回と同じになります。
これで、盤面の状況は Game
コンポーネントにある state
型の history
配列に記録できるようになりました。history
配列の要素は、ある手番における盤面の状況を示します。具体的には、history[i]
という要素は i
回目の手番における盤面の状況、history[i][0]
は盤面の左上にあるマスの状態を示します。
おわりに
今回は、タイムトラベルを実装するにあたり Game
コンポーネントを定義したリフトアップをしていきました。次のページに現段階のソースファイルを示します。
次回は、Game
コンポーネントにテキストを過去の手番に戻れるようにしていきます。