Следуя учебнику по реагированию, чтобы создать игру в крестики-нолики, и она работает, никаких проблем. Я понимаю, почему почти все это работает, кроме отображения победителя.
import { useState } from 'react';
export default function Board() {
const [xIsNext, setXIsNext] = useState(true);
const [squares, setSquares] = useState(Array(9).fill(null));
function handleClick(idx) {
const nextSquares = squares.slice();
if (nextSquares[idx] || calculateWinner(squares)) {
return;
}
nextSquares[idx] = xIsNext ? 'X' : '0';
setSquares(nextSquares);
setXIsNext(!xIsNext);
}
let winner = calculateWinner(squares);
let status = winner
? 'Winner: ' + winner
: 'Next player: ' + (xIsNext ? 'X' : '0');
return (
<>
<div className = "status">{status}</div>
<div className = "board-row">
<Square val = {squares[0]} onSquareClick = {() => handleClick(0)} />
<Square val = {squares[1]} onSquareClick = {() => handleClick(1)} />
<Square val = {squares[2]} onSquareClick = {() => handleClick(2)} />
</div>
<div className = "board-row">
<Square val = {squares[3]} onSquareClick = {() => handleClick(3)} />
<Square val = {squares[4]} onSquareClick = {() => handleClick(4)} />
<Square val = {squares[5]} onSquareClick = {() => handleClick(5)} />
</div>
<div className = "board-row">
<Square val = {squares[6]} onSquareClick = {() => handleClick(6)} />
<Square val = {squares[7]} onSquareClick = {() => handleClick(7)} />
<Square val = {squares[8]} onSquareClick = {() => handleClick(8)} />
</div>
</>
);
}
Обновление квадратов понятно — устанавливается состояние и обновляется компонент Square
.
Чего я не понимаю, так это того, как пересчитывается status
. Что заставляет этот небольшой блок кода устанавливать winner
и status
снова запускаться? Это обновление состояния? Если это будет запущено снова, то определение xIsNext
и друзей должно произойти снова?
Здесь есть какая-то магия, которую я не понимаю, и она не объяснена в руководстве (что в остальном действительно ясно).
Большое спасибо за понимание.
Что запускает этот небольшой блок кода для повторного запуска победителя и статуса? Это обновление состояния?
Да. Обновления состояния вызывают повторный рендеринг, а повторный рендеринг снова выполняет всю функцию Board()
. Функциональность хуков React (useState
, useEffect
и т. д.) внутренне управляет тем, что он делает при последующих рендерингах/выполнениях, поскольку они выполняются при каждом рендеринге.
Простые операторы присваивания в теле компонента назначаются при каждом рендеринге.
Кстати, это может иметь большое значение, если логика внутри компонента предполагает тяжелую обработку/расчеты/и т.д. Если разработчика удивит тот факт, что весь компонент выполняется повторно, возможно, много раз, это может привести к значительным проблемам с производительностью. Чтобы смягчить это, полезно использовать такие вещи, как useCallback
, useMemo
и т. д.
В React вычисление и обновление статуса являются частью цикла рендеринга компонента. Вот описание того, как это работает:
Рендеринг компонентов и обновления состояния:
Когда вы вызываете setSquares(nextSquares) или setXIsNext(!xIsNext), React планирует повторный рендеринг компонента Board. Это означает, что функция компонента (т. е. Board) вызывается снова с обновленными значениями состояния.
Перерасчет статуса:
Во время каждого рендеринга переменные статуса и победителя пересчитываются на основе текущих значений состояния (квадратов и xIsNext). Строка let Winner = CalcultWinner(squares); запускается каждый раз при рендеринге компонента Board. Аналогично, пусть статус = победитель? 'Победитель:' + победитель: 'Следующий игрок:' + (xIsNext? 'X': '0'); пересчитывается при каждом рендеринге.
Ререндеринг React:
Когда вы обновляете состояние с помощью setSquares или setXIsNext, React запускает повторный рендеринг компонента Board. Во время повторного рендеринга React снова вызывает функцию Board. Это приводит к перерасчету переменных победителя и статуса на основе обновленного состояния.
Вы видите, как React обрабатывает изменения состояния и повторную визуализацию. Каждый раз, когда состояние обновляется, React повторно выполняет функцию Board, поэтому статус пересчитывается с учетом нового состояния. Таким образом, каждый раз, когда состояние меняется, React повторно отображает компонент, снова запуская функцию Board и пересчитывая статус и победителя с обновленным состоянием. Вот почему отображаемый статус всегда соответствует последнему состоянию.
Чего я не понимаю, так это того, как пересчитывается статус. Что запускает этот небольшой блок кода для повторного запуска победителя и статуса?
Это обновление состояния?
Если это запустить еще раз, то определение xIsNext и его друзей должно произойти снова?
"любое понимание"
.
import { useState } from 'react';
const statuses = {
IN_PROGRESS: 'IN_PROGRESS',
FINISHED: 'FINISHED'
};
const players = {
X: 'X',
O: 'O'
};
const getNewBoard = () => {
return [
[null, null, null],
[null, null, null],
[null, null, null]
];
};
const calculateWinner = (board = []) => {
// check every row and column
for (let i = 0; i < 3; i++) {
const rowCheck = [...new Set(board[i])];
if (rowCheck.length === 1 && rowCheck[0] !== null) {
return rowCheck[0];
}
const colCheck = [...new Set(board.reduce((col, row) => [...col, row[i]], []))];
if (colCheck.length === 1 && colCheck[0] !== null) {
return colCheck[0];
}
}
// check main diagonal
const mainDiag = [...new Set([board[0][0], board[1][1], board[2][2]])];
if (mainDiag.length === 1 && mainDiag[0] !== null) {
return mainDiag[0];
}
// check secondary diagonal
const secDiag = [...new Set([board[0][2], board[1][1], board[2][0]])];
if (secDiag.length === 1 && secDiag[0] !== null) {
return secDiag[0];
}
};
const Board = () => {
const [board, setBoard] = useState(getNewBoard());
const [status, setStatus] = useState(statuses.IN_PROGRESS);
const [playerTurn, setPlayerTurn] = useState(players.X);
const [winner, setWinner] = useState(null);
const startNewGame = () => {
setBoard(getNewBoard());
setStatus(statuses.IN_PROGRESS);
setPlayerTurn(players.X);
setWinner(null);
};
const makeTurn = (row, col) => {
if (status === statuses.FINISHED || board[row][col]) {
return;
}
const newBoard = board.map((row) => row.slice());
newBoard[row][col] = playerTurn;
const winner = calculateWinner(newBoard);
setBoard(newBoard);
if (winner) {
setStatus(statuses.FINISHED);
setWinner(winner);
return;
}
setPlayerTurn(playerTurn === players.X ? players.O : players.X);
};
return (
<>
<div className = "status">
{status === statuses.IN_PROGRESS && `Next player: ${playerTurn}`}
{status === statuses.FINISHED && `Winner: ${winner}`}
</div>
{board.map((row, rowNum) => {
return (
<div className = "board-row" key = {rowNum}>
{row.map((value, colNum) => {
return (
<Square key = {colNum} val = {value} onSquareClick = {() => makeTurn(rowNum, colNum)} />
);
})}
</div>
);
})}
<div>
<button onClick = {startNewGame}>
{status === statuses.IN_PROGRESS ? 'Restart game' : 'New game'}
</button>
</div>
</>
);
};
export default Board;