reactjs - 使用反应钩子实现井字游戏的历史功能时遇到问题
问题描述
我目前正在尝试使用钩子进行井字游戏项目。我在实现“历史”功能时遇到问题:
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
解决方案
有两个小问题,需要解决。你的反应部分是正确的。问题是:
- 在您的手柄点击中,您需要创建一个新数组,而不是使用旧数组。
- 切片参数需要在句柄历史记录中有所不同才能获得所需的输出。
在您的代码中添加了注释以供理解。希望对您有所帮助。
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>
推荐阅读
- git - 如果我使用多个远程存储库(Github 和 BitBucket),其他合作者会知道吗?
- javascript - 根据交替的相关下拉列表更改 Ajax Post 参数和返回的 HTML
- mocha.js - 从 Mocha 测试的报告输出中删除已执行的 SQL 查询
- sql - SQL 将 2 行合并为 1 行,然后删除 1
- html - 嵌入在网页中的 Google 表格
- c++ - 如果我到达文件c ++的末尾,我如何关闭文件
- python - 为什么水平线仅部分显示在 sage / matplotlib 中?
- html - 对于我的登录页面,我有一张图片作为其背景。但是一旦你登录它就会转移到游戏中并阻止它
- javascript - 如何在 NGINX 上重定向并将原始请求存储为 POST 变量?
- c# - Websocket 代码在控制台中有效,但在 winform 中无效