useState 钩子的“setState”一次调用发生两次

yon*_*oni 4 arrays reactjs react-hooks use-state

有一个带有方块的板,它们的值依赖于一个数组,它是用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;
    }
    );
  }
Run Code Online (Sandbox Code Playgroud)

日志:

the 3,0 square was clicked 
before: 0 
after: 1 
the 3,0 square was clicked 
before: 1 
after: 2 
before: 2 
after: 3 
Run Code Online (Sandbox Code Playgroud)

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

HMR*_*HMR 6

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

let newBoard = [...prevValues];
newBoard[rowIndex] = [...newBoard[rowIndex]];
newBoard[rowIndex][cellIndex] =
newBoard[rowIndex][cellIndex] + 1;
Run Code Online (Sandbox Code Playgroud)

  • 我同意,这是更好的解决方案 (2认同)
  • @yoni 你正在改变状态,这可能会破坏重新渲染。有关如何不改变状态的更多信息可以在[此处](https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns)找到 (2认同)

tan*_*may 5

正如 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;
});
Run Code Online (Sandbox Code Playgroud)

如果你想玩一下,我已经在codesandbox中模拟了它。

  • @yoni如果你已经在使用lodash,你可以使用“cloneDeep”进行深度克隆,“JSON.parse(JSON.stringify(...))”也可以,如果你有一个较小的对象,我认为,或者你可以去与 HMR 其他答案中的方法相同 (2认同)