提问人:lukeyp 提问时间:8/30/2022 最后编辑:lukeyp 更新时间:8/30/2022 访问量:109
使用随机数的 React Strict 模式
Using React Strict mode with random numbers
问:
我正在尝试使用 react 用 AI 创建一个井字游戏。我的 AI 有一个函数,可以重新调整随机行和列以放置一块。但是,当使用严格模式时,AI 需要两回合,因为随机数会再次生成。从我所做的阅读来看,这表明我错误地更新了我的电路板状态,但我不确定我哪里出了问题。以下是我在板上放置标记的代码。
const handlePlacePiece = (row: number, col: number) => {
let currentPiece = board[row][col];
if (currentPiece === "-") {
const newBoard = Array.from(board);
newBoard[row][col] = currentPlayer; // The current players mark either 'X' or 'O'
setBoard(newBoard);
setCurrentPlayer(currentPlayer === "X" ? "O" : "X");
}
};
这是我最初的董事会状态:
const [board, setBoard] = useState([
["-", "-", "-"],
["-", "-", "-"],
["-", "-", "-"],
]);
这是我的 ai 函数:
export function easyAi(board: any) {
let col = getRandomMove(); //Math.floor(Math.random() * 3);
let row = getRandomMove();
while (board[row][col] !== "-") {
col = getRandomMove();
row = getRandomMove();
}
return { row, col };
}
调用 handlePlacePiece(这也是一个 onClick,但这会产生正确的结果):
if (gameType === "AI") {
if (currentPlayer === aiPiece) {
const { row, col } = easyAi(board);
handlePlacePiece(row, col);
}
}
GitHub 上的完整文件: https://github.com/lukeypap/Tic-Tac-Toe-React/blob/master/src/components/Board/index.tsx
如果您需要更多详细信息,请告诉我,谢谢。
答:
问题
“AI”轮到它的逻辑就在函数体中,这是一个无意的副作用。这意味着无论何时,无论何时出于任何原因呈现组件,它调用的所有代码。将某些功能运行两次,以帮助您检测意外的副作用。
React.StrictMode
if (gameType === "AI" && !gameOver) { if (checkDraw(board) || checkWinner(board)) { console.log("setting game over true"); setGameOver(true); } else if (currentPlayer === aiPiece && !gameOver) { const { row, col } = easyAi(board); handlePlacePiece(row, col); } }
React.StricMode
严格模式无法自动为您检测副作用,但它可以通过使它们更具确定性来帮助您发现它们。这是通过有意双重调用以下函数来完成的:
- 类组件 、 和方法
constructor
render
shouldComponentUpdate
- 类组件静态方法
getDerivedStateFromProps
- 功能组件主体 <--
- 状态更新程序函数(第一个参数
setState
) - 传递给 、 或 的函数
useState
useMemo
useReducer
- 类组件 、 和方法
回调正在改变状态对象。所有正在更新的状态和嵌套状态都应该被浅层复制,以便创建新的引用。回调仅创建外部数组的浅拷贝,但所有行数组仍是对先前状态的引用。
handlePlacePiece
board
board
const newBoard = Array.from(board); newBoard[row][col] = currentPlayer; // <-- mutation! setBoard([...newBoard]);
溶液
使用功能状态更新从以前的值更新板状态,用于浅拷贝正在更新的行和列。Array.prototype.map
将“AI”转换逻辑移动到钩子中。我建议将游戏检查也移动到一个钩子中,以便在状态更新时调用。useEffect
useEffect
board
建议:
enum PIECE {
X = "X",
O = "O",
EMPTY = "-",
}
interface props {
gameType: "AI" | "HUMAN";
selectedPiece: PIECE;
startingPlayer: PIECE;
}
const index = ({ gameType, selectedPiece, startingPlayer }: props) => {
const [board, setBoard] = useState(
Array(3).fill(Array(3).fill(PIECE.EMPTY))
);
const [currentPlayer, setCurrentPlayer] = useState(startingPlayer);
const [aiPiece, setAiPiece] = useState(
selectedPiece === PIECE.O ? PIECE.X : PIECE.O
);
const [gameOver, setGameOver] = useState(false);
const [checkingMove, setCheckingMove] = useState(false);
const handlePlacePiece = (row: number, col: number) => {
let mark = board[row][col];
if (mark === PIECE.EMPTY) {
setCheckingMove(true);
setBoard((board) =>
board.map((boardRow: PIECE[], i: number) =>
i === row
? boardRow.map((boardCol: PIECE, j: number) =>
j === col ? currentPlayer : boardCol
)
: boardRow
)
);
}
};
useEffect(() => {
const isWinner = checkWinner(board); // *
const isDraw = checkDraw(board);
if (isWinner || isDraw) {
if (isWinner) {
console.log(`Winner is: ${isWinner}`);
} else if (isDraw) {
console.log("Draw");
}
setGameOver(true);
} else {
setCurrentPlayer((current) => (current === PIECE.X ? PIECE.O : PIECE.X));
setCheckingMove(false);
}
}, [board]);
useEffect(() => {
if (
!checkingMove &&
gameType === "AI" &&
!gameOver &&
currentPlayer === aiPiece
) {
const { row, col } = easyAi(board);
handlePlacePiece(row, col);
}
}, [checkingMove, currentPlayer, aiPiece, gameType, gameOver, board]);
return (
...
);
};
* 注意:该实用程序已更新为返回获胜作品。checkWinner
export const checkWinner = (board: string[][]) => {
//Rows
if (
board[0][0] == board[0][1] &&
board[0][1] == board[0][2] &&
board[0][0] != "-"
) {
return board[0][0];
}
if (
board[1][0] == board[1][1] &&
board[1][1] == board[1][2] &&
board[1][0] != "-"
) {
return board[1][0];
}
if (
board[2][0] == board[2][1] &&
board[2][1] == board[2][2] &&
board[2][0] != "-"
) {
return board[2][0];
}
//Cols
if (
board[0][0] == board[1][0] &&
board[1][0] == board[2][0] &&
board[0][0] != "-"
) {
return board[0][0];
}
if (
board[0][1] == board[1][1] &&
board[1][1] == board[2][1] &&
board[0][1] != "-"
) {
return board[0][1];
}
if (
board[0][2] == board[1][2] &&
board[1][2] == board[2][2] &&
board[0][2] != "-"
) {
return board[0][2];
}
//Diags
if (
board[0][0] == board[1][1] &&
board[1][1] == board[2][2] &&
board[0][0] != "-"
) {
return board[0][0];
}
if (
board[0][2] == board[1][1] &&
board[1][1] == board[2][0] &&
board[0][2] != "-"
) {
return board[0][2];
}
};
评论
useEffect
评论
handlePlacePiece
setCurrentPlayer(currentPlayer === "X" ? "O" : "X");
setCurrentPlayer(current => current === "X" ? "O" : "X");