はじめに
Reactのチュートリアルで勉強しようと思って
三目並べやったことある人なら共感してもらえるはず、
最初はいいけど「巻き戻し機能」を実装するところから一気に意味が分からなくなり、
2日間ぐらいコードを読んでましたw
コメントアウトしすぎて逆に見ずらいと思いますが
三目並べで苦戦している人がピンポイントでわからないことなどあれば参考になるかなと
このままで投稿します。
コード・コメントアウト
import { useState } from 'react';
function Square({ value, onSquareClick }) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
);
}
function Board({ xIsNext, squares, onPlay }) {
function handleClick(i) {
// 勝者が存在するもしくは、クリック済みだったら早期リターン=処理が実行されない
if (calculateWinner(squares) || squares[i]) {
return;
}
// この場合のslice()は配列全体をコピーする。これによりイミュータリビティを実現する。
const nextSquares = squares.slice();
// true=偶数=X、false=奇数=〇
if (xIsNext) {
nextSquares[i] = 'X';
} else {
nextSquares[i] = 'O';
}
// 「今回の手」を引数にいれhandlePlayを呼び出す
onPlay(nextSquares);
}
// calculateWinner関数を呼び出し、〇もしくは×もしくはnullが返る。それを元に条件分岐
const winner = calculateWinner(squares);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (xIsNext ? 'X' : 'O');
}
return (
<>
<div className="status">{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>
</>
);
}
// トップレベルのコンポーネント
export default function Game() {
// 履歴の保持
const [history, setHistory] = useState([Array(9).fill(null)]);
// 何手目かを保持
const [currentMove, setCurrentMove] = useState(0);
// 今の手が偶数ならtrue,奇数ならfalse
const xIsNext = currentMove % 2 === 0;
// 今の手の配列Boardコンポーネントに渡して表示を制御
const currentSquares = history[currentMove];
function handlePlay(nextSquares) {
// スプレット演算子[...]新しい配列nextHistoryに過去の履歴と新しい状態を統合
// 現在の手までの履歴をsliceで切りだし(巻き戻ししたところまで)、新しい配列を追加させる
const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
// 履歴の保存=9つの値を格納した配列をどんどん追加していく。
// [Array(9),Array(9),Array(9)] ←3手目までの履歴
setHistory(nextHistory);
// 3手目だった場合、2とcurrentMoveに保存
setCurrentMove(nextHistory.length - 1);
}
// setCurrentMoveに巻き戻ししたい手数を代入し、巻き戻す
// 仕組みはconst currentSquares = history[currentMove]; でbordの局面を制御しているため、引数の局面を指定することで渡す配列を変えることができる
function jumpTo(nextMove) {
setCurrentMove(nextMove);
}
// 巻き戻し機能のためのボタンをmapで展開し、htmlで作成
// moveに何手目なのかを格納し、moveの値で条件分岐でdescriptionに入れるテキストを切り替える
// onClickにアロー関数を設置し、クリックしたらjumpToが発火
const moves = history.map((squares, move) => {
let description;
if (move > 0) {
description = 'Go to move #' + move;
} else {
description = 'Go to game start';
}
return (
<li key={move}>
{/* moveには何手目かを格納しているのでjumpToにmoveを渡して関数を実行する */}
<button onClick={() => jumpTo(move)}>{description}</button>
</li>
);
});
return (
<div className="game">
<div className="game-board">
{/* propsで3つのデータをBoardコンポーネントに渡す */}
<Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
</div>
<div className="game-info">
{/* {moves}に履歴の配列展開してhtmlにして格納している為、出力するとボタンがあるだけ表示される */}
<ol>{moves}</ol>
</div>
</div>
);
}
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];
// aに×または〇が入っていて、なおかつb,cが同じ値(×または〇)の場合
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
// 上記の条件に当てはまっている場合、×または〇を返す
return squares[a];
}
}
return null;
}
参考になれば幸いです。