В настоящее время я пытаюсь сделать проект Tic Tac Toe, используя хуки. У меня проблемы с реализацией функциональности "история":
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.info(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





Есть две небольшие проблемы, которые необходимо исправить. Вы правильно поняли часть React. Проблемы:
Добавили комментарии в ваш код для понимания. Надеюсь, это будет полезно для вас.
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>