首页 > 解决方案 > 使用反应钩子实现井字游戏的历史功能时遇到问题

问题描述

我目前正在尝试使用钩子进行井字游戏项目。我在实现“历史”功能时遇到问题:

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.scss";

const App = props => {
  const [turn, setTurn] = useState("X");
  const [history, setHistory] = useState([Array(9).fill(null)]);
  const [winner, setWinner] = useState(null);

  const latestBoard = history[history.length - 1];

  const handleClick = i => {
    if (latestBoard[i] !== null) {
      return;
    }

    latestBoard.splice(i, 1, turn);
    setHistory([...history, [...latestBoard]]);
    setTurn(turn === "O" ? "X" : "O");
  };

  const handleHistory = index => {
    if (index === 1) {
      setHistory([Array(9).fill(null)]);
      return;
    }

    let newHistory = history.slice(0, index);
    setHistory(newHistory);
    console.log(history);
  };

  const calcWinner = squares => {
    /*...*/
  };

  return (
    <div className="container">
      <GameBoard onClick={idx => handleClick(idx)} squares={latestBoard} />
      <Sidebar
        winner={winner}
        turn={turn}
        history={history}
        goBack={idx => handleHistory(idx)}
      />
    </div>
  );
};

const GameBoard = ({ squares, onClick }) => {
  const grid = squares.map((item, idx) => {
    return (
      <div key={idx} onClick={() => onClick(idx)}>
        {item}
      </div>
    );
  });

  return <div className="grid-container">{grid}</div>;
};

const Sidebar = ({ winner, turn, history, goBack }) => {
  let historyList = history.map((pos, idx) => {
    return idx >= 1 ? ( 
      <li key={idx} onClick={() => goBack(idx)}>
        {idx}
      </li>
    ) : null;
  });

  let info;

  if (winner) {
    info = `The winner is ${winner}`;
  } else {
    info = `${turn} it's your turn`;
  }

  return (
    <div className="sidebar">
      <div className="gameInfo">{info}</div>
      <ul className="history-dropdown"> Go to move {historyList}</ul>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);



每次单击列表项调用 handleHistory 时,直到第二次单击才生效

我在这里有一个代码示例:https ://codesandbox.io/embed/q75y24qj49?fontsize=14

标签: reactjsreact-hooks

解决方案


有两个小问题,需要解决。你的反应部分是正确的。问题是:

  1. 在您的手柄点击中,您需要创建一个新数组,而不是使用旧数组。
  2. 切片参数需要在句柄历史记录中有所不同才能获得所需的输出。

在您的代码中添加了注释以供理解。希望对您有所帮助。

const { useState, useEffect } = React


const App = props => {
  const [turn, setTurn] = useState("X");
  const [history, setHistory] = useState([Array(9).fill(null)]);
  const [winner, setWinner] = useState(null);

  const latestBoard = history[history.length - 1];

  const handleClick = i => {
    if (latestBoard[i] !== null) {
      return;
    }
    //latestBoard.splice(i, 1, turn);
    //setHistory([...history, [...latestBoard]]);
    // latestBoard and history are same
    // reference hence you are spreading the same array
    // you need to do something like this below
    const copylatestBoard = [...latestBoard]; // Because array are passed by
    // reference 1st change
    copylatestBoard.splice(i, 1, turn); // 2nd change

    setHistory([...history, [...copylatestBoard]]); //3rd change
    setTurn(turn === "O" ? "X" : "O");
  };

  const handleHistory = index => {
    // if (index === 1) {
    //   setHistory([Array(9).fill(null)]);
    //   return;
    // }
    let newHistory = history.slice(0, index + 1); // need to be index + 1 to slice desired
    // as history at 0 is blank board. I am sure it will be obvious now
    setHistory(newHistory);
  };

  const calcWinner = squares => {
    /*...*/
  };

  return (
    <div className="container">
      <GameBoard onClick={idx => handleClick(idx)} squares={latestBoard} />
      <Sidebar
        turn={turn}
        history={history}
        goBack={idx => handleHistory(idx)}
      />
    </div>
  );
};

const GameBoard = ({ squares, onClick }) => {
  const grid = squares.map((item, idx) => {
    return (
      <div key={idx} onClick={() => onClick(idx)}>
        {item}
      </div>
    );
  });

  return <div className="grid-container">{grid}</div>;
};

const Sidebar = ({ winner, turn, history, goBack }) => {
  let historyList = history.map((pos, idx) => {
    return idx >= 1 ? ( // to start after the first click
      <li key={idx} onClick={() => goBack(idx)}>
        {idx}
      </li>
    ) : null;
  });

  let info;

  if (winner) {
    info = `The winner is ${winner}`;
  } else {
    info = `${turn} it's your turn`;
  }

  return (
    <div className="sidebar">
      <div className="gameInfo">{info}</div>
      <ul className="history-dropdown"> Go to move {historyList}</ul>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
body {
	 font-family: sans-serif;
	 text-align: center;
	 display: flex;
	 justify-content: center;
}
 .container {
	 border: 1px solid black;
	 width: 25em;
	 height: 90vh;
	 max-height: 18em;
	 display: flex;
	 justify-content: center;
}
 .container > * {
	 border: 1px solid black;
	 margin: 1em;
}
 .grid-container {
	 flex: 5;
	 display: grid;
	 grid-template-columns: 1fr 1fr 1fr;
	 grid-template-rows: 1fr 1fr 1fr;
	 grid-template-areas: ". . ." ". . ." ". . .";
	 position: relative;
	 height: 80%;
}
 .grid-container > * {
	 border: 1px solid black;
}
 .grid-container > *:hover {
	 cursor: pointer;
}
 .sidebar {
	 padding: 1em;
}
 .sidebar .history-dropdown {
	 transition: all 600ms ease-in-out;
}
 .sidebar .history-dropdown > *:hover {
	 cursor: pointer;
	 border: 1px solid black;
}
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>

<div id="root"></div>


推荐阅读