使用状态钩子的"设置状态"一次调用出现两次



有一块带有正方形的板,它们的值依赖于一个数组,用useState钩子处理。每次单击都应该使值增加一,但不幸的是,它增加了两(除了第一次单击(。

我的问题是:

(1( 为什么会发生这种情况,(2(如何避免,通常,(3(有没有更好的方法来处理这样一个带有钩子的数组。

let emptyBoard = Array.from({ length: parseInt(props.rows, 10) }, () =>
new Array(parseInt(props.columns, 10)).fill(0)
);
const [squaresValues, setSquaresValue] = useState(emptyBoard);
function onClick(id) {
const [rowIndex, cellIndex] = id;
console.log("the " + id + " square was clicked");
setSquaresValue(prevValues => {      
let newBoard = [...prevValues];
console.log("before: " + newBoard[rowIndex][cellIndex]);
newBoard[rowIndex][cellIndex] = newBoard[rowIndex][cellIndex] + 1;
console.log("after: " + newBoard[rowIndex][cellIndex]);
return newBoard;
}
);
}

日志:

the 3,0 square was clicked 
before: 0 
after: 1 
the 3,0 square was clicked 
before: 1 
after: 2 
before: 2 
after: 3 

可以看出,从第二次单击开始,每次单击都会使值升高两次。

您仍在改变状态,如果您有纯组件,那么它们在改变时不会重新渲染。如果您有纯组件,那么使用JSON.parse进行完整状态复制是个坏主意,因为所有内容都将被重新渲染。

let newBoard = [...prevValues];
newBoard[rowIndex] = [...newBoard[rowIndex]];
newBoard[rowIndex][cellIndex] =
newBoard[rowIndex][cellIndex] + 1;
正如Udaya Prakash在上面的评论中提到的,它被调用了两次,以确保您的setState是独立的和幂等的。所以,如果我理解正确的话,它被调用两次并不是一个错误,但你的值在第二次被更改是。

以下是Dan Abramov对同一期GitHub的评论:

预计setState更新程序将在开发中以严格模式运行两次。这有助于确保代码不依赖于它们一次运行(如果异步渲染被中止并重新启动,情况就不会如此(。如果您的setState更新程序是纯函数(应该是纯函数(,那么这不应该影响应用程序的逻辑。

我们可以通过深度复制prevValues来修复它,而不是使用排列运算符进行浅层复制。正如你可能已经知道的,有多种方法可以深度复制你的对象,我们现在可以使用JSON.parse(JSON.stringify(...),你可以从这里用你最喜欢的代替它

setSquaresValue(prevValues => {
let newBoard = JSON.parse(JSON.stringify(prevValues)); // <<< this
console.log("before: " + newBoard[rowIndex][cellIndex]);
newBoard[rowIndex][cellIndex] = newBoard[rowIndex][cellIndex] + 1;
console.log("after: " + newBoard[rowIndex][cellIndex]);
return newBoard;
});

如果你想玩的话,我已经在这里的代码沙盒中模拟过了。

最新更新