在本篇技术博客中,我们将介绍一个React官方示例:井字棋游戏。我们将逐步讲解代码实现,包括游戏的组件结构、状态管理、胜者判定以及历史记录功能。让我们一起开始吧!
项目概览
在这个井字棋游戏中,我们有以下组件:
Square
组件:表示游戏棋盘上的每一个方格。Board
组件:表示游戏的棋盘,包含了多个Square
组件。Game
组件:表示整个游戏的容器,包含了游戏状态的管理和历史记录功能。
Square
组件
Square
组件表示游戏棋盘上的每一个方格。代码如下:
function Square({ value, onSquareClick }) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
);
}
Square
组件接收两个props:value
和onSquareClick
。value
表示当前方格的取值('X'、'O'或null),onSquareClick
是一个点击事件处理函数。当方格被点击时,onSquareClick
将会被触发。
Board
组件
Board
组件表示整个游戏的棋盘,包含了多个Square
组件。代码如下:
function Board({ xIsNext, squares, onPlay }) {
function handleClick(i) {
if (calculateWinner(squares) || squares[i]) {
return;
}
const nextSquares = squares.slice();
if (xIsNext) {
nextSquares[i] = 'X';
} else {
nextSquares[i] = 'O';
}
onPlay(nextSquares);
}
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>
</>
);
}
Board
组件接收三个props:xIsNext
、squares
和onPlay
。xIsNext
表示当前轮到哪个玩家下棋('X'或'O'),squares
是一个数组,表示游戏的棋盘状态,每个元素为方格的取值。onPlay
是一个函数,用于更新游戏的棋盘状态。
Board
组件内部定义了handleClick
函数,用于处理方格的点击事件。若游戏已经有胜者或方格已经被占用,则不进行任何操作。否则,根据当前的玩家('X'或'O'),更新方格的取值,然后调用onPlay
函数更新游戏状态。
Board
组件还根据当前的棋盘状态判断游戏的状态,并将其显示在页面上。
Game
组件
Game
组件是整个游戏的容器,它负责管理游戏的状态和历史记录。代码如下:
export default function Game() {
const [history, setHistory] = useState([Array(9).fill(null)]);
const [currentMove, setCurrentMove] = useState(0);
const xIsNext = currentMove % 2 === 0;
const currentSquares = history[currentMove];
function handlePlay(nextSquares) {
const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
setHistory(nextHistory);
setCurrentMove(nextHistory.length - 1);
}
function jumpTo(nextMove) {
setCurrentMove(nextMove);
}
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}>
<button onClick={() => jumpTo(move)}>{description}</button>
</li>
);
});
return (
<div className="game">
<div className="game-board">
<Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
</div>
<div className="game-info">
<ol>{moves}</ol>
</div>
</div>
);
}
Game
组件使用了useState
钩子来管理游戏的状态。history
是一个数组,用于保存游戏的历史记录,每个元素是一个表示棋盘状态的数组。currentMove
表示当前的步数,xIsNext
表示当前轮到哪个玩家下棋('X'或'O'),currentSquares
是当前步数对应的棋盘状态。
handlePlay
函数用于处理玩家的下棋操作。它会将下一个棋盘状态添加到history
中,并更新当前的步数。
jumpTo
函数用于跳转到历史记录中的特定步数。
Game
组件还渲染了一个历史记录列表,用于显示每一步的操作。点击列表中的按钮可以跳转到对应的历史记录步数。
辅助函数 calculateWinner
最后,我们还有一个辅助函数 calculateWinner
,用于判断游戏是否有胜者。代码如下:
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;
}
calculateWinner
函数接收一个squares
数组,表示游戏的棋盘状态。它遍历一个包含所有获胜情况的数组lines
,检查是否有任何一种情况下三个方格的取值相同,如果有则返回该取值('X'或'O'),否则返回null
表示没有胜者。
总结
在这篇技术博客中,我们详细介绍了React官方示例井字棋游戏。我们了解了游戏的组件结构,实现了游戏状态的管理、胜者判定和历史记录功能。通过这个简单的井字棋游戏,我们学习了React组件之间的通信、状态管理以及如何处理用户交互。