React:状态对象中缺少 属性
React: Missing property in state object
所以我开始学习 React,在做了一些基础知识(计数器、待办事项列表)之后,我决定做一些更具挑战性的事情;棋盘上的棋子可以通过点击移动。我的主要成分是Chessboard
。然后呈现可能包含 Piece
的 Tiles
。有关游戏的信息存储在 Chessboard's
状态,如下所示:
interface State {
board: Record<string, pieceInterface>;
isPieceMoving: boolean;
movingPieceId: string;
}
我用 Chessboard's
方法 onTileClick(id: string)
处理移动的棋子。它作为道具传递给 Tile
,当点击一个图块时,它会使用图块 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)
以查看错误发生的位置。即使我把它直接放在 else 后面,它仍然说“a3”是 undefined
.
我花了几个小时试图了解发生了什么。我试图在控制台中模拟这段代码,但它总是有效!谁能向我解释我做错了什么?这是我还没有学过的一些奇怪的 React 的 setState 机制吗?还是我不知道的一些基本 javascript 东西?任何帮助我们都会很棒,因为我被困在这里无法继续前进并做任何其他事情。
编辑 #1
好的。所以我能够通过 深度复制 state.board
到 board
来修复这个错误(灵感来自@Linda Paiste 的评论)。我只是使用了简单的 JSON stringify/parse hack:
let board: Record<string, pieceInterface> = JSON.parse(JSON.stringify(state.board));
这修复了错误,但我不知道为什么。我会保留这个问题,所以也许有人会向我解释 what 和 why 我以前的代码是错误的。
编辑#2
好的。感谢@Linda 的解释和阅读 the docs 我终于明白我做错了什么。 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],
}
}));
State
board
属性 的打字稿类型应该是 Partial<Record<string, pieceInterface>>
因为不是棋盘上的每个插槽都有一块。
很丑但很实用Code Sandbox demo
编辑:关于 setState 回调
I heard that using setState
with a function makes state
a copy of this.state
, so I can mutate it.
这是不正确的。你永远不应该改变状态,这也不例外。使用回调的优点是保证 state
参数是当前值,这很重要,因为 setState
是异步的并且可能有多个挂起的更新。
以下是 React docs 所说的(强调):
state
is a reference to the component state at the time the change is being applied. It should not be directly mutated. Instead, changes should be represented by building a new object based on the input from state
and props
."
Both state
and props
received by the updater function are guaranteed to be up-to-date.
所以我开始学习 React,在做了一些基础知识(计数器、待办事项列表)之后,我决定做一些更具挑战性的事情;棋盘上的棋子可以通过点击移动。我的主要成分是Chessboard
。然后呈现可能包含 Piece
的 Tiles
。有关游戏的信息存储在 Chessboard's
状态,如下所示:
interface State {
board: Record<string, pieceInterface>;
isPieceMoving: boolean;
movingPieceId: string;
}
我用 Chessboard's
方法 onTileClick(id: string)
处理移动的棋子。它作为道具传递给 Tile
,当点击一个图块时,它会使用图块 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)
以查看错误发生的位置。即使我把它直接放在 else 后面,它仍然说“a3”是 undefined
.
我花了几个小时试图了解发生了什么。我试图在控制台中模拟这段代码,但它总是有效!谁能向我解释我做错了什么?这是我还没有学过的一些奇怪的 React 的 setState 机制吗?还是我不知道的一些基本 javascript 东西?任何帮助我们都会很棒,因为我被困在这里无法继续前进并做任何其他事情。
编辑 #1
好的。所以我能够通过 深度复制 state.board
到 board
来修复这个错误(灵感来自@Linda Paiste 的评论)。我只是使用了简单的 JSON stringify/parse hack:
let board: Record<string, pieceInterface> = JSON.parse(JSON.stringify(state.board));
这修复了错误,但我不知道为什么。我会保留这个问题,所以也许有人会向我解释 what 和 why 我以前的代码是错误的。
编辑#2
好的。感谢@Linda 的解释和阅读 the docs 我终于明白我做错了什么。 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],
}
}));
State
board
属性 的打字稿类型应该是 Partial<Record<string, pieceInterface>>
因为不是棋盘上的每个插槽都有一块。
很丑但很实用Code Sandbox demo
编辑:关于 setState 回调
I heard that using
setState
with a function makesstate
a copy ofthis.state
, so I can mutate it.
这是不正确的。你永远不应该改变状态,这也不例外。使用回调的优点是保证 state
参数是当前值,这很重要,因为 setState
是异步的并且可能有多个挂起的更新。
以下是 React docs 所说的(强调):
state
is a reference to the component state at the time the change is being applied. It should not be directly mutated. Instead, changes should be represented by building a new object based on the input fromstate
andprops
."Both
state
andprops
received by the updater function are guaranteed to be up-to-date.