【问题标题】:React: why is the state returned from useState stays the same when the initializer function gets called on every re-render?React:为什么在每次重新渲染时调用初始化函数时从 useState 返回的状态保持不变?
【发布时间】:2020-09-06 19:50:08
【问题描述】:

这是现场演示:https://codesandbox.io/s/strange-bash-qj7ld?file=/src/App.js

所以我有一个这样的组件

const fn = () => {
  console.log("useState");
  return ["string"];
};

export default function App() {
  const [counter, setCounter] = useState(0);

  const [nonprimitive, setNonprimitive] = useState(fn());

  useEffect(() => {
    console.log("useEffect");
  }, [nonprimitive]);

  return (
    <>
      <button onClick={() => setCounter(counter + 1)}>Counter {counter}</button>
      <div>{nonprimitive[0]}</div>
    </>
  );
}

每次点击按钮,计数器都会增加,组件会重新渲染。然后再次调用fn(),然后它会返回一个数组。当nonprimitive 发生变化时会触发useEffect。我有两个问题:

  1. 为什么useEffect 只被触发一次,而不是每次后续调用? nonprimitive 是一个数组,它是一种非原始类型,因此即使其中的字符串项保持不变,它也应该每次都不同。看起来 React 正在做一些深入的比较。
  2. 为什么每当组件重新渲染时useState 会被打印两次?即,当我按下按钮时,fn 内的 console.log("useState"); 会被调用两次。

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

    为什么useEffect 只触发一次,而不是每次后续调用? nonprimitive 是一个数组,它是一种非原始类型,因此即使其中的字符串项保持不变,它也应该每次都不同。看起来 React 正在做一些深入的比较。

    useState 的重点是在组件的渲染中保持值。当你的组件第一次渲染时,useState(fn()) 被调用并且nonprimitive 得到值["string"]。在随后的渲染中,useState(fn()) 再次被调用(因为毕竟它仍然是一个标准的函数调用,所以日志再次发生),但 React 知道这不是你的第一次渲染,所以它丢弃了参数( fn) 的返回值,而是返回之前存储的值,这在引用上是相同的。

    如果您不希望 React 在每次渲染时都调用初始状态创建(例如,如果您的初始状态非常复杂或计算耗时),您可以向它传递一个只会调用的函数初始渲染。

    const [nonprimitive, setNonprimitive] = useState(() => fn());
    

    在您的情况下,由于初始状态已经包装在一个函数中,您可以等效地编写

    const [nonprimitive, setNonprimitive] = useState(fn);
    

    为什么每当组件重新渲染时,useState 会被打印两次?即,当我按下按钮时,fn 内的 console.log("useState"); 会被调用两次。

    如果您查看您的 index.js,您会注意到您的 App 组件包含在 &lt;React.StrictMode&gt; 中。 React strict mode 会故意多次运行某些渲染,以帮助您捕获代码中的副作用和挂钩违规。

    【讨论】:

    • 赞成你对 useState 的描述
    • 感谢您的回复。所以你指出,因为 React 知道 fn 不是第一次被调用,所以它会丢弃它的返回值,而是返回之前的状态。但是我想知道fn 返回的值是否会随着时间的推移而改变,例如,可能是一个随机数,React 仍然会忽略它的返回值吗?我以为 React 会对返回的值做一些比较,但听起来 React 甚至懒得比较来自 fn 的返回值
    • 您传递给useState 的参数只是initial 值。如果要更改nonprimitive 的值,请使用setNonprimitive
    【解决方案2】:

    双重渲染是React.StrictMode的结果:

    严格模式无法自动为您检测副作用,但可以通过使它们更具确定性来帮助您发现它们。这是通过有意双重调用以下函数来完成的:

    • 类组件构造函数、渲染和 shouldComponentUpdate 方法
    • 类组件静态getDerivedStateFromProps方法
    • 函数组件体
    • 状态更新函数(setState 的第一个参数)
    • 传递给 useState、useMemo 或 useReducer 的函数

    如果您从索引文件中删除 React.StrictMode,您将不再看到双重渲染。

    至于useEffect,你是对的。发生了深刻的比较。 React 能够推断出数组没有改变,除非数组的元素发生了改变。另请记住,提供给useState 的任何内容都只会在第一次渲染时设置,因此在调用设置器之前,您的数组实际上不会改变,即使fn 被多次调用。

    React 严格模式文档:https://reactjs.org/docs/strict-mode.html

    【讨论】:

    • React 不执行深度比较,效果不会运行两次,因为状态永远不会改变并且(引用)相同的数组正在返回。
    猜你喜欢
    • 1970-01-01
    • 2023-01-18
    • 2020-05-15
    • 2017-01-14
    • 2020-11-30
    • 2017-03-25
    • 2023-02-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多