· 5 min read

React 18,新的严格模式

好好的 useMemo、useEffect 居然执行了两次,我明明传入了依赖,为什么会执行两次呢?原来是 React 18 的破坏性改动!

之前在开发模式时,一直记得 useMemo 在严格模式下不会二次执行, useEffect 在有传入 deps 时也不会二次执行。而今天我在排下面这段代码时,发现一个要命的事情!

const camera = useMemo(/* .. */, []);
const renderer = props; // from parent

const controls = useMemo(
  () => new OrbitControls(camera, renderer.domElement),
  [camera, renderer],
);

useEffect(() => {
  // 同一个 controls 进入两次
  return () => {
    controls.dispose(); // 执行一次,controls 被释放
  };
}, [controls]);

controls 生成了两个,第一个似乎没用到了,第二个是后续要正常使用的对象,并且进入了 useEffect 中,而且进去了两次! 这导致两个严重的问题就是,第一个 controls 没有被销毁,第二个 controls 被销毁了! 第一个没销毁是内存泄漏,第二个被销毁了导致 controls 对象不可用了!

我一度以为是我对 useEffect 特性的记忆出现了偏差,后来我在官方文档翻了半天没啥收获,指到我看见了更新说明里的 Stricter Strict Mode

新的严格模式

从更新日志可以看到,新的严格模式会自动卸载并再次重新挂载每个组件,这就解释了为什么 useMemouseEffect 即使在有传入 deps 也会多执行一次。

这个特性是破坏性的,会影响之前版本的程序逻辑,所以 React 在更新说明也建议如果旧的应用因为这个出现兼容性问题,建议先关掉 strictMode

在 React 18 的测试代码

React 18 Stricter Strict Mode.png

代码:Code Sandbox

红色是第一次渲染,绿色是第二次渲染。输出日志里淡些的是 React 18 在二次调用时输出的 log 的默认效果,在 React 17 中是被默认隐藏的。蓝色指向的是最终应用在界面上呈现的结果。

我在 V 站上也提出了我的疑问,根据大佬们的回复,我总结了一下:

  1. useMemo(() => /* */, []) 执行一此后,以新的严格模式的规则,进行了二次调用,第一次的值作废。
  2. useEffect(() => /* */, [])执行一此后,以新的严格模式的规则,调用了 destructor 后,进行了二次调用。

在第 2 点中,两次 useEffect 都是使用同一个值,是因为严格模式的二次调用按钩子分别执行两次,所以 useMemo 两次的调用都完毕后,得到的值再被 useEffect 执行两次。我调整了一下代码,将测试代码复制了一份在后面,可以看到 “useMemo” 和 “useMemo 2” 先执行了一次,又再执行了一次,然后再到 “useEffect“ 和 “useEffect 2”: 加倍快乐

结论

useEffect 应该独自管理副作用,要做到自己创建,自己销毁。

const camera = useMemo(/* .. */, []);
const renderer = props; // from parent

const [controls, setControls] = useState<OrbitControls | null>(null);
useEffect(() => {
  const controls = new OrbitControls(camera, renderer.domElement) // 自己的锅
  setControls(controls);
  return () => {
    controls.dispose(); // 自己背
  };
}, [camera, renderer]);

今天深究了一下这个问题,解决方案其实我也知道,但是之前的写法突然以我不理解的方式失效了,还是要较个劲,万一是 React 不规范呢?(狗头

Share:
Back to Blog

Related Posts

View All Posts »
2023 年,再组一台黑苹果 ITX 主机

2023 年,再组一台黑苹果 ITX 主机

这是第二次组黑苹果台式机了,上一次是第一次,所以保守地选择了 i5-10400 + B460M + 6600XT 的组合。因为最近感受到 CPU 性能有一些吃紧,并且 ITX 的遗憾又开始涌上心头,最后看到 V 站老哥出 10700K + Z490I。顺势入手了。但是夜长梦多,挑选了半天的配件,最后发现应该买 12600K 比较合适。只能友好地鸽掉了 TAT

在 PVE 宿主机上使用桌面环境

在 PVE 宿主机上使用桌面环境

虽然 PVE 宿主机不应该安装乱七八糟的东西,但是我穷,为了物尽其用,为了在主力电脑翻车时有一个立即可用的备用环境,所以还是安装了基础的桌面环境。现在的 Linux 桌面环境越来越好了,我选择安装 KDE Plasma 作为桌面环境,并且默认关闭,按需启用。