Почему крестики-нолики React показывают победителя?

Следуя учебнику по реагированию, чтобы создать игру в крестики-нолики, и она работает, никаких проблем. Я понимаю, почему почти все это работает, кроме отображения победителя.

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 и друзей должно произойти снова?

Здесь есть какая-то магия, которую я не понимаю, и она не объяснена в руководстве (что в остальном действительно ясно).

Большое спасибо за понимание.

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
0
0
55
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Что запускает этот небольшой блок кода для повторного запуска победителя и статуса? Это обновление состояния?

Да. Обновления состояния вызывают повторный рендеринг, а повторный рендеринг снова выполняет всю функцию 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;

Другие вопросы по теме