因此,我开始学习React,并做了一些基础知识(计数器,待办事项列表)后,我决定做一些更具挑战性的事情。带有棋子的棋盘,可以通过单击来移动。我的主要组成部分是Chessboard
。然后渲染Tiles
可能包含的渲染Piece
。有关游戏的信息以Chessboard's
状态存储,如下所示:
interface State {
board: Record<string, pieceInterface>;
isPieceMoving: boolean;
movingPieceId: string;
}
我用Chessboard's
方法来处理移动的部分onTileClick(id: string)
。它Tile
作为道具传递给a ,并在单击图块时使用图块ID对其进行调用(例如“ a4”,“ f6”,“ h3”)。它具有以下逻辑。游戏可以处于两种状态:我当前正在移动一个棋子,或者我目前什么都不做,然后通过单击它开始移动一个棋子。当我开始移动作品时,我将其ID(或者更确切地说是女巫架子上的瓷砖的ID)存储在中state.movingPieceId
。当我尝试放置它时,我会检查图块是否为空,然后进行相应的更改state.board
。这是我的代码:
onTileClick = (id: string): void => {
if (!this.state.isPieceMoving) {
if (this.state.board[id]) {
this.setState({
isPieceMoving: true,
movingPieceId: id,
});
}
} else {
if (!this.state.board[id]) {
this.setState((state) => {
let board: Record<string, pieceInterface> = state.board;
const piece: pieceInterface = board[state.movingPieceId];
delete board[state.movingPieceId];
board[id] = piece;
// console.log(board[id]);
// console.lob(board);
const isPieceMoving: boolean = false;
const movingPieceId: string = "";
return { board, isPieceMoving, movingPieceId };
});
}
}
};
第一部分工作正常。但是在第二个中,有一些我找不到的错误。当我取消注释此控制台日志时,输出看起来像这样(我想将片段从“ a2”移动到“ a3”):
Object { "My piece representation" }
Object {
a1: { "My piece representation" },
a3: undefined,
a7: { "My piece representation" },
...
}
我的代码正确地从“ a2”(板上没有“ a2”属性)“获取”了单击的片段,但显然并没有将其放回原处。即使打印时board[id]
给出正确的对象!我将日志记录console.log(board)
向上移动,以查看发生错误的位置。即使我将它直接放在其他位置,它仍然说“ a3”是undefined
。
我花了几个小时试图了解正在发生的事情。我试图在控制台中模拟这段代码片段,但它始终可以正常工作!有人可以向我解释我在做什么错吗?这是我还没有学过的一些怪异的React的setState机制吗?还是我不知道的一些基本的javascript内容?我们会提供任何帮助,因为我被困在这里无法继续前进并做其他任何事情。
编辑#1
好的。所以,我是能够解决受到此问题的深拷贝 state.board
到board
(由@Linda Paiste的评论inspiredy)。我只是使用了简单的JSON stringify / parse hack:
let board: Record<string, pieceInterface> = JSON.parse(JSON.stringify(state.board));
这修复了该错误,但我不知道为什么。我将继续公开这个问题,所以也许有人会向我解释我先前的代码出了什么问题以及为什么出了问题。
编辑#2
好的。感谢@Linda的解释和阅读文档,我终于了解了我做错了什么。琳达在下面的回答(被接受的答案)中做了详细的解释。非常感谢大家!
let board: Record<string, pieceInterface> = state.board;
您正在创建一个新变量board
,该变量引用的板对象与实际状态下的板对象相同。然后,您对该对象进行突变:
delete board[state.movingPieceId];
board[id] = piece;
这是非法的状态变更,因为对状态的任何更改board
也会影响状态this.state.board
。
您要在状态中更改的任何数组或对象都必须是该对象的新版本。
您的board
作品的深层副本,因为此新作品board
完全独立于您,state
因此您可以对其进行变异而不会影响状态。但这不是最好的解决方案。它效率低下,并且会导致不必要的渲染,因为每个块对象也将是新版本。
我们只需要复制正在更改的对象:state
和board
。
this.setState((state) => ({
isPieceMoving: false,
movingPieceId: '',
board: {
// copy everything that isn't changing
...state.board,
// remove the moving piece from its current position
[state.movingPieceId]: undefined,
// place the moving piece in its new location
[id]: state.board[state.movingPieceId],
}
}));
您的打字稿类型的board
属性State
应该是Partial<Record<string, pieceInterface>>
因为并非板上的每个插槽都有一块。
非常丑陋但功能强大的Code Sandbox演示
我听说
setState
与函数配合使用可state
复制this.state
,因此可以对其进行突变。
那是不对的。您永远都不应改变状态,这也不例外。使用回调的优点state
是保证参数是当前值,这很重要,因为它setState
是异步的,并且可能有多个挂起的更新。
这是React文档说的(重点已添加):
state
是在应用更改时对组件状态的引用。它不应该直接突变。相反,改变应该通过建立一个代表新对象基于从输入state
和props
“。二者
state
并props
通过更新功能接收保证是向上的更新。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句