React官方教程井字棋游戏附加练习

附加练习

React 的官方教程中,有一个井字棋的游戏,在最后给出了几个挑战练习:

  1. For the current move only, show “You are at move #…” instead of a button
  2. Rewrite Board to use two loops to make the squares instead of hardcoding them.
  3. Add a toggle button that lets you sort the moves in either ascending or descending order.
  4. 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).
  5. 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 组件更新的理解。