【问题标题】:Can React's useState setter lambdas run multiple times?React 的 useState setter lambda 可以多次运行吗?
【发布时间】:2021-12-06 21:05:41
【问题描述】:

说实话,除了“这里发生了什么?”之外,我一直在努力想出一种表达这个问题的方法。采用以下旨在将增量项目添加到列表的 React 代码:

import React, { useState } from "react";
import "./styles.css";

let counter = 0;

export default function App() {
  const [list, setList] = useState([]);

  console.info("Render:", counter, list.join());

  return (
    <div className="App">
      {list.join()}
      <button
        onClick={() => {
          setList((prevList) => {
            console.info("Pre-push:", counter, prevList.join());
            const newList = [...prevList, "X" + ++counter];
            console.info("Post-push:", counter, newList.join());
            return newList;
          });
        }}
      >
        Push
      </button>
    </div>
  );
}

如果您使用https://codesandbox.io/s/amazing-sea-6ww68?file=/src/App.js 运行该代码并单击“Push”按钮四次,我希望看到“X1”然后是“X1,X2”然后是“X1,X2,X3”,然后是“X1,X2” ,X3,X4”。够简单吧?相反,它会渲染“X1”然后是“X1,X3”然后是“X1,X3,X5”然后是“X1,X3,X5,X7”。

现在我想,“嗯,可能增加counter 的函数被调用了两次?”,所以我添加了控制台日志,你看到了,这让我更加困惑。在控制台中,我看到:

Render: 0 "" 
Pre-push: 0 "" 
Post-push: 1 X1 
Render: 1 X1 
Pre-push: 1 X1 
Post-push: 2 X1,X2 
Render: 2 X1,X2 
Pre-push: 3 X1,X3 
Post-push: 4 X1,X3,X4 
Render: 4 X1,X3,X4 
Pre-push: 5 X1,X3,X5 
Post-push: 6 X1,X3,X5,X6 
Render: 6 X1,X3,X5,X6 

请注意,控制台中的连接列表与 React 呈现的连接列表不匹配,没有记录表明 counter 如何从 2 -> 3 和 4 -> 5 碰撞,以及尽管我只追加到列表中,但列表却发生了神秘的变化。

值得注意的是,如果我将++counter 移出setList 委托,它会按预期工作:

import React, { useState } from "react";
import "./styles.css";

let counter = 0;

export default function App() {
  const [list, setList] = useState([]);

  console.info("Render:", counter, list.join());

  return (
    <div className="App">
      {list.join()}
      <button
        onClick={() => {
          ++counter;
          setList((prevList) => {
            console.info("Pre-push:", counter, prevList.join());
            const newList = [...prevList, "X" + counter];
            console.info("Post-push:", counter, newList.join());
            return newList;
          });
        }}
      >
        Push
      </button>
    </div>
  );
}

这里到底发生了什么?我怀疑这与 React 纤程和useState 的内部实现有关,但我仍然完全不知道如何在没有控制台日志之前和之后显示此类证据的情况下增加counter,除非React 实际上是在覆盖 console 以便它可以选择性地抑制日志,这看起来很疯狂......

【问题讨论】:

    标签: reactjs use-state react-fiber


    【解决方案1】:

    它似乎被调用了两次,因为它是。

    在严格模式下运行时,在开发模式下运行时 React intentionally invokes the following methods twice

    严格模式不能自动为您检测副作用,但它可以通过使它们更具确定性来帮助您发现它们。这是通过有意双重调用以下函数来完成的:
    • 类组件构造函数、渲染和 shouldComponentUpdate 方法
    • 类组件静态getDerivedStateFromProps方法
    • 函数组件体
    • 状态更新函数(setState 的第一个参数)
    • 传递给 useState、useMemo 或 useReducer 的函数

    不确定 console.log 调用会发生什么,但我敢打赌,如果您切换到生产模式,这个问题就会消失。

    【讨论】:

    • 就是这样,我现在能够通过防止 React 覆盖 console.info 来验证这一点,方法是:codesandbox.io/s/xenodochial-mendeleev-2lh6q?file=/src/… 在该页面下方,文档确实说: “注意:从 React 17 开始,React 会自动修改 console.log() 等控制台方法,以在第二次调用生命周期函数时使日志静音。但是,在某些可以使用变通方法的情况下,它可能会导致不良行为。”
    • 啊。明白了。我认为他们正在吞下它,但错过了文档中的那一点。干杯。
    猜你喜欢
    • 2019-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-20
    • 1970-01-01
    • 2019-11-12
    • 2022-07-31
    相关资源
    最近更新 更多