'setState' 个 useState 挂钩在一次调用中出现两次
'setState' of useState hook occurs twice by one call
有一个带有方块的棋盘,它们的值依赖于一个数组,它是用 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
可以看出,从第二次点击开始,每次点击该值都会提高两次。
正如 Udaya Prakash 在上面的评论中提到的,它被调用了两次以确保您的 setState 是独立且幂等的。所以,如果我理解正确的话,它被调用两次不是一个错误,但是你的值被第二次改变是。
这是同一 GitHub 期 Dan Abramov 的评论:
It is expected that setState updaters will run twice in strict mode in development. This helps ensure the code doesn't rely on them running a single time (which wouldn't be the case if an async render was aborted and alter restarted). If your setState updaters are pure functions (as they should be) then this shouldn't affect the logic of your application.
我们可以通过深度复制您的 prevValues
来修复它,而不是使用扩展运算符进行浅复制。您可能已经知道,有多种方法可以深度复制您的对象,我们现在可以使用 JSON.parse(JSON.stringify(...)
,您可以将其替换为 your favorite kind from here
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;
});
如果你想玩的话,我已经在codesandbox here中模拟了它。
你还在改变状态,如果你有纯组件,那么它们在改变时不会重新渲染。如果你有纯组件,用 JSON.parse 做完整的状态复制是个坏主意,因为一切都会被重新渲染。
let newBoard = [...prevValues];
newBoard[rowIndex] = [...newBoard[rowIndex]];
newBoard[rowIndex][cellIndex] =
newBoard[rowIndex][cellIndex] + 1;
有一个带有方块的棋盘,它们的值依赖于一个数组,它是用 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
可以看出,从第二次点击开始,每次点击该值都会提高两次。
正如 Udaya Prakash 在上面的评论中提到的,它被调用了两次以确保您的 setState 是独立且幂等的。所以,如果我理解正确的话,它被调用两次不是一个错误,但是你的值被第二次改变是。
这是同一 GitHub 期 Dan Abramov 的评论:
It is expected that setState updaters will run twice in strict mode in development. This helps ensure the code doesn't rely on them running a single time (which wouldn't be the case if an async render was aborted and alter restarted). If your setState updaters are pure functions (as they should be) then this shouldn't affect the logic of your application.
我们可以通过深度复制您的 prevValues
来修复它,而不是使用扩展运算符进行浅复制。您可能已经知道,有多种方法可以深度复制您的对象,我们现在可以使用 JSON.parse(JSON.stringify(...)
,您可以将其替换为 your favorite kind from here
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;
});
如果你想玩的话,我已经在codesandbox here中模拟了它。
你还在改变状态,如果你有纯组件,那么它们在改变时不会重新渲染。如果你有纯组件,用 JSON.parse 做完整的状态复制是个坏主意,因为一切都会被重新渲染。
let newBoard = [...prevValues];
newBoard[rowIndex] = [...newBoard[rowIndex]];
newBoard[rowIndex][cellIndex] =
newBoard[rowIndex][cellIndex] + 1;