React 18 useEffect 执行多次

2022/8/6 react

# 现象

在使用 React 18 开发应用的时候发现了一个问题就是在 useEffectdeps[] 的情况下,里面的内容居然执行了两边。在之前的版本中可不是这么个现象,React18 之前这种情况下应该只会执行一次才是。

useEffect(() => {
  console.log("First call on mount...");
  return () => console.log("Cleanup...");
}, []);

// React18 控制台输出
First call on mount...
Cleanup...
First call on mount...

// React18 之前控制台输出
First call on mount...
1
2
3
4
5
6
7
8
9
10
11
12

# 原因

在官方文档的 Ensuring reusable state (opens new window) 中给出了问题出现的原因:React 18 在严格模式的开发环境下会执行一个 check:在一个组件第一次被 mount 的时候,会将组件自动的 unmount 再 remount,在第二次 mount 的时候把组件恢复到之前的状态。

看起来很奇怪的一个设定,实际上是因为 React 是在为后续要添加功能铺路,后面可能允许 React 在保留状态的同时添加或删除部分 UI。比如为了支持在不同的 Tab 之间进行切换的时候可以立刻显示之前的内容,React 支持再次 mount 的时候使用组件 unmount 之前的状态。

# 解决方案

问题既然已经出现,而且大方向算是已经定好,看起来初衷也是不错的(虽然操作让开发者不一定满意,网上相关内容还不少),就看怎么解决了。大多数的组件在这种模式下都是正常的,只有部分情况下是需要处理的,不同场景下可以采取不同的方式进行处理。

# dom 挂载

在 useEffect 中执行挂载 dom 的操作会被挂载两次,这个问题只要在 clearup 中执行销毁操作,就可以保证不会重复挂载。这个操作实际上是本来就应该要做的,只是如果默认加载一次的情况下很多人或许就会遗漏了这一点。

# 数据请求

在组件挂载的时候或者根据条件不同进行请求数据的场景也是非常常见的,正常情况下的期望都是只要发送一次请求就够了,如果发送多次请求除了增加服务器压力外更有可能会导致错误。关于数据请求可以参考 React 中发送请求的方式

# 关闭严格模式

上面两种方案是针对特殊场景的,如果一个项目中很多组件都出现问题需要处理,一个简单解决方案是禁用严格模式,打开 src/index.js 文件并删除 StrictMode 高阶组件。

实际严格模式是有很大作用的,所以一般都不应该禁用掉,这种情况下更应该考虑组件的实现上是不是有什么不合理的地方。

# 其他方案

还可以不使用默认的 useEffect,自定义个一个 hook 处理类似的情况,可以参考 React 18 - Avoiding Use Effect Getting Called Twice (opens new window)React 18, useEffect is getting called two times on mount (opens new window) 中的回答。

# 参考