React 18 StrictMode第一次useEffect错误状态

fra*_*lat 5 javascript reactjs react-strictmode

另一个 React 18 严格模式问题。我知道 React 将调用渲染和效果函数两次,以突出即将推出的功能中潜在的内存泄漏。我还不明白的是如何正确处理这个问题。我的问题是,我无法正确卸载第一个渲染结果,因为两个 useEffect 调用是使用第二个渲染的状态执行的。这是一个例子来展示我的意思。


  const ref = useRef(9);
  const id = useId();

  console.log('@@ initial id', id);
  console.log('@@ initial ref', ref.current);

  ref.current = Math.random();

  console.log('@@ random ref', ref.current);

  useEffect(() => {
    console.log('@@ effect id', id);
    console.log('@@ effect ref', ref.current);

    return () => {
      console.log('@@ unmount id', id);
      console.log('@@ unmount ref', ref.current);
    };
  });
Run Code Online (Sandbox Code Playgroud)

这是日志输出

@@ initial id :r0:
@@ initial ref 9
@@ random ref 0.26890444169781214
@@ initial id :r1:
@@ initial ref 9
@@ random ref 0.7330565878991766
@@ effect id :r1:                 <<--- first effect doesn't use data of first render cycle
@@ effect ref 0.7330565878991766
@@ unmount id :r1:
@@ unmount ref 0.7330565878991766
@@ effect id :r1:
@@ effect ref 0.7330565878991766
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,第一个渲染周期的状态没有 useEffect 调用,第二个渲染周期也没有为您提供第一个渲染周期的引用(它再次用 9 初始化,而不是 0.26890444169781214。此外useId 钩子返回两个不同的 id,其中第二个 Id 也保留在进一步的渲染周期中。这是一个错误还是预期行为?如果是预期的,有没有办法解决这个问题?

Ali*_*man 0

在 React 18 之前StrictMode,您的组件只能安装一次。但现在,它们安装、卸载,然后重新安装。因此,它不仅是运行两次的效果 - 整个组件也被渲染两次。

这意味着你的状态被重新初始化,你的裁判也被重新初始化。显然,你的效果也会运行两倍。

关于运行两次的效果,您需要正确清理async效果 - 任何异步执行某些操作的效果,例如从服务器获取数据、添加事件侦听器等。并非所有效果都需要清理。

此外,这些效果应该在开发中运行两次(它们在生产中只运行一次)。有些人试图阻止效果运行两次,但这是不行的。如果正确清理效果,当它在生产中运行一次或在开发中运行两次时,其执行应该没有差异。

此外,useId 钩子返回两个不同的 id,其中第二个 id 也保留在进一步的渲染周期中。这是错误还是预期行为?如果是预期的话,有办法解决这个问题吗?第二个值将是所使用的值。这不是一个错误,您可以继续使用它作为“真实”值。

StrictMode 您可以在这里阅读更多内容。

编辑:检测卸载。

// Create a ref to track unmount
const hasUnmounted = useRef(false)

useEffect(() => {
  return () => {
    // Set ref value to true in cleanup. This will run when the component is unmounted. If this is true, your component has unmounted OR the effect has run at least once
    hasUnmounted.current = true;
  }
}, [])
Run Code Online (Sandbox Code Playgroud)

  • 这只是重复网上写的内容,对我没有帮助。不管怎么说,多谢拉。我仍然有与渲染周期不匹配的效果。 (2认同)
  • 检测卸载不会像我预期的那样工作。该函数组件被调用两次并初始化两个新的引用。然后useEffect将被调用,destroy fn将被调用,useEffect将被再次调用。两个 useEffect 循环都将使用创建的第二个引用。因此,第一个渲染周期被完全隐藏,在那里创建的任何内容都会丢失,并且不会执行相应的效果调用。 (2认同)