附加练习
React 的官方教程中,有一个井字棋的游戏,在最后给出了几个挑战练习:
- For the current move only, show “You are at move #…” instead of a button
- Rewrite Board to use two loops to make the squares instead of hardcoding them.
- Add a toggle button that lets you sort the moves in either ascending or descending order.
- When someone wins, highlight the three squares that caused the win (and when no one wins, display a message about the result being a draw).
- Display the location for each move in the format (row, col) in the move history list.
我的代码
今天重新复习了这个教程游戏,下面是我的最终实现。
import { useState } from 'react';
function Square({ value, squareClick, isWinner }) {
let css = 'square';
if (isWinner) css = 'square winner';
return (
<button className={css} onClick={squareClick}>
{value}
</button>
);
}
function Board({ xIsNext, squares, onPlay, currentMove }) {
const { winner, lines } = calculateWinner(squares);
let status = `Next player: ${xIsNext ? 'X' : 'O'}`;
if (winner) status = `Winner is: ${winner}`;
else if (currentMove === 9) status = `the result being a draw`;
function handleClick(i, rIndex, cIndex) {
const { winner } = calculateWinner(squares);
if (winner || squares[i]) return;
const tmp = squares.slice();
tmp[i] = xIsNext ? 'X' : 'O';
onPlay(tmp, rIndex, cIndex);
}
const rows = [0, 1, 2].map((rIndex) => {
const items = [0, 1, 2].map((cIndex) => {
const index = rIndex * 3 + cIndex;
return (
<Square
key={index}
value={squares[index]}
squareClick={() => handleClick(index, rIndex, cIndex)}
isWinner={lines.includes(index)}
/>
);
});
return (
<div className="board-row" key={rIndex}>
{items}
</div>
);
});
return (
<>
<div className="status">{status}</div>
{rows}
</>
);
}
export default function Game() {
const [history, setHistory] = useState([Array(9).fill(null)]);
const [currentMove, setCurrentMove] = useState(0);
const [point, setPoint] = useState([['', '']]);
const [isAsc, setIsAsc] = useState(true);
const xIsNext = currentMove % 2 === 0;
const currentSquares = history[currentMove];
function handlePaly(nextSquares, rIndex, cIndex) {
const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
setHistory(nextHistory);
setCurrentMove(nextHistory.length - 1);
setPoint([...point, [rIndex, cIndex]]);
}
function jumpTo(nextMove) {
// if (nextMove === currentMove) return;
setCurrentMove(nextMove);
}
let moves = history.map((squares, move) => {
const index = isAsc ? move : history.length - 1 - move;
if (index > 0 && currentMove === index)
return (
<li key={index} className="mb-5">
{`You are at move #${index} point(${point[index][0]}, ${point[index][1]})`}
</li>
);
let description;
if (index > 0) {
description = `Go to move #${index} point(${point[index][0]}, ${point[index][1]})`;
} else {
description = 'Go to game start';
}
return (
<li key={index} className="mb-5">
<button onClick={() => jumpTo(index)}>{description}</button>
</li>
);
});
function handleSort() {
setIsAsc(!isAsc);
}
return (
<div className="game">
<div className="game-board">
<Board
xIsNext={xIsNext}
squares={currentSquares}
onPlay={handlePaly}
currentMove={currentMove}
/>
</div>
<div className="game-info">
<button className="sort" onClick={handleSort}>
{isAsc ? 'Asc Sort' : 'Desc Sort'}
</button>
<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];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return {
winner: squares[a],
lines: lines[i]
};
}
}
return {
winner: null,
lines: []
};
}
添加了几个 css 样式:
.sort {
margin-left: 20px;
width: 100px;
}
.mb-5 {
margin-bottom: 5px;
padding: 5px;
}
.winner {
color: #ff0000;
}
What I Learned
很久没写 React 了,手生,重温了一遍教程,找找感觉。加深了对 React 组件更新的理解。