发布于 2026-01-06 0 阅读
0

为什么 React 18 会破坏你的应用?DEV 的全球展示挑战赛由 Mux 呈现:展示你的项目!

为什么 React 18 会破坏你的应用

由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!

你刚刚完成了React 18 的升级,经过一些简单的质量保证测试后,没有发现任何问题。“升级很简单,”你心想。

不幸的是,之后你收到了一些来自其他开发人员的内部错误报告,这些报告表明你的防抖钩子似乎工作不正常。你决定创建一个最小复现示例,并演示该钩子的功能。

你以为等待一秒钟后会弹出一个“警告”对话框,但奇怪的是,对话框根本没有运行。

请在沙箱中查看并运行相关代码示例。

这很奇怪,因为上周它在你的电脑上还运行正常!为什么会这样?发生了什么变化?

你的应用在 React 18 中崩溃的原因是你在使用StrictMode

只需打开你的index.js(或index.ts)文件,并更改以下代码:

render(
  <StrictMode>
    <App />
  </StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

阅读起来就是这样:

render(
    <App />
);
Enter fullscreen mode Exit fullscreen mode

React 18 中似乎引入到你的应用程序中的所有错误都突然消失了。

只有一个问题:这些错误是真实存在的,并且在 React 18 之前就存在于你的代码库中——你只是没有意识到而已。

部件损坏的证据

回顾我们之前的示例,我们在第 56 - 60 行中使用React 18 的createRootAPI来渲染包装器App内部的内容StrictMode

请在沙箱中查看并运行相关代码示例。

目前,按下按钮没有任何反应。但是,如果移除

StrictMode重新加载页面Alert后,可以看到一秒钟的防抖效果。

查看代码,让我们console.log在代码中添加一些 s useDebounce,因为我们的函数应该在那里被调用。

function useDebounce(cb, delay) {
  const inputsRef = React.useRef({ cb, delay });
  const isMounted = useIsMounted();
  React.useEffect(() => {
    inputsRef.current = { cb, delay };
  });
  return React.useCallback(
    _.debounce((...args) => {
        console.log("Before function is called", {inputsRef, delay, isMounted: isMounted()});
          if (inputsRef.current.delay === delay && isMounted())
                      console.log("After function is called");
                  inputsRef.current.cb(...args);
        }, delay),
    [delay]
  );
}
Enter fullscreen mode Exit fullscreen mode
Before function is called Object { inputsRef: {…}, delay: 1000, isMounted: false }

哦!看来isMounted它从未被设置为 true,因此inputsRef.current回调函数没有被调用:而这正是我们想要进行防抖处理的函数。

我们来看一下useIsMounted()代码库:

function useIsMounted() {
  const isMountedRef = React.useRef(true);
  React.useEffect(() => {
    return () => {
          isMountedRef.current = false;
    };
  }, []);
  return () => isMountedRef.current;
}
Enter fullscreen mode Exit fullscreen mode

这段代码乍一看似乎很有道理。毕竟,虽然我们在返回函数中进行清理以useEffect在首次渲染时将其移除,useRef但的初始设置器会在每次渲染开始时运行,对吧?

嗯,也不完全是。

React 18 有哪些变化?

在旧版本的 React 中,组件只需挂载一次即可。因此,`is`useRef和 `is`的初始值useState几乎可以视为设置一次后就被遗忘了。

在 React 18 中,React 开发团队决定改变这种行为,在严格模式下,每个组件会被多次重新挂载。这主要是因为 React 未来某个潜在的功能将会采用这种行为。

React 团队希望在未来的版本中添加一项名为“可复用状态”的功能。可复用状态的基本思想是,如果一个标签页组件被卸载(例如用户切换到其他标签页时),然后又被重新挂载(例如用户切换到其他标签页时),React 会恢复分配给该组件的数据。由于这些数据可以立即使用,因此可以毫不犹豫地立即渲染相应的组件。

因此,虽然例如数据内部的数据useState可能会被持久化,但必须正确地清理和处理副作用。引用 React 文档

此功能将使 React 具有更好的开箱即用性能,但要求组件能够承受多次挂载和销毁的效果。

然而,React 18 中严格模式下的这种行为转变不仅仅是 React 团队为了保护未来而采取的措施:它也是提醒人们要正确遵守 React 的规则,并按预期清理自己的操作。

毕竟,React 团队自己也早就警告过,空的依赖数组[]作为第二个参数)不能保证它只运行一次。

事实上,这篇文章的标题可能有些误导——React 团队表示,他们已经升级了 Facebook 核心代码库中的数千个组件,而且没有出现重大问题。很可能,绝大多数应用程序都能顺利升级到最新版本的 React。

尽管如此,这些 React 错误还是会悄悄地渗入我们的应用程序中。虽然 React 团队可能预计不会出现太多导致应用程序崩溃的情况,但这些错误似乎相当普遍,因此有必要做出解释。

如何修复重新挂载错误

我之前链接的代码是我在生产应用程序中编写的,它是错误的。useRef我们不能只依赖一次初始化,而需要确保初始化在每次实例化时都运行useEffect

function useIsMounted() {
  const isMountedRef = React.useRef(true);
  React.useEffect(() => {
  isMountedRef.current = true; // Added this line  
  return () => {
      isMountedRef.current = false;
    };
  }, []);
  return () => isMountedRef.current;
}
Enter fullscreen mode Exit fullscreen mode

反过来也一样!我们需要确保对之前可能遗漏的任何组件进行清理。

许多人忽略了这条规则,对于App他们不打算重新挂载的根元素,但随着新的严格模式行为的出现,这种保证不再是安全的。

要解决应用中的此问题,请查找以下迹象:

  • 会产生副作用,但需要清理,而且没有设置(就像我们的例子一样)
  • 未妥善清理造成的副作用
  • 利用[]假设所述代码只会运行useMemo一次useEffect

删除此代码后,您的应用程序应该可以完全正常运行,并且可以在应用程序中重新启用严格模式!

结论

React 18 带来了许多令人惊艳的新功能,例如新的 Suspense 特性新的 useId hook自动批处理等等。虽然为了支持这些功能而进行的重构工作有时可能会令人沮丧,但重要的是要记住,它们能为用户带来实实在在的好处。

例如,React 18 还引入了一些功能来防止渲染抖动,以便在需要处理快速用户输入时创造更好的体验。

有关 React 18 升级过程的更多信息,请参阅我们的 React 18 升级指南。

文章来源:https://dev.to/coderpad/why-react-18-broke-your-app-4730