tailwind-nextjs-blog/data/blog/react-18-stricter-strict-mode.md

75 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: React 18新的严格模式
date: '2023-02-06'
tags: ['React']
draft: false
summary: 好好的 useMemo、useEffect 居然执行了两次,我明明传入了依赖,为什么会执行两次呢?原来是 React 18 的破坏性改动!
---
之前在开发模式时,一直记得 `useMemo` 在严格模式下不会二次执行, `useEffect` 在有传入 `deps` 时也不会二次执行。而今天我在排下面这段代码时,发现一个要命的事情!
```tsx
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](https://github.com/facebook/react/blob/main/CHANGELOG.md#1820-june-14-2022:~:text=Stricter%20Strict%20Mode)。
## 新的严格模式
从更新日志可以看到,新的严格模式会自动卸载并再次重新挂载每个组件,这就解释了为什么 `useMemo``useEffect` 即使在有传入 `deps` 也会多执行一次。
这个特性是破坏性的,会影响之前版本的程序逻辑,所以 React 在[更新说明](https://github.com/facebook/react/blob/main/CHANGELOG.md#1820-june-14-2022:~:text=If%20this%20breaks%20your%20app%2C%20consider%20removing%20Strict%20Mode%20until%20you%20can%20fix%20the%20components%20to%20be%20resilient%20to%20remounting%20with%20existing%20state.)也建议如果旧的应用因为这个出现兼容性问题,建议先关掉 `strictMode`
### 在 React 18 的测试代码
![React 18 Stricter Strict Mode.png](https://minio.ivanli.cc/ivan-public/uPic/2023/TWVx7v.png)
代码:[Code Sandbox](https://codesandbox.io/p/sandbox/clever-cache-pm1oct?file=%2Fsrc%2FApp.tsx&selection=%5B%7B%22endColumn%22%3A20%2C%22endLineNumber%22%3A33%2C%22startColumn%22%3A20%2C%22startLineNumber%22%3A33%7D%5D)
红色是第一次渲染,绿色是第二次渲染。输出日志里淡些的是 React 18 在二次调用时输出的 log 的默认效果,在 React 17 中是被默认隐藏的。蓝色指向的是最终应用在界面上呈现的结果。
我在 V 站上也提出了我的[疑问](https://v2ex.com/t/913595),根据大佬们的回复,我总结了一下:
1. `useMemo(() => /* */, [])` 执行一此后,以新的严格模式的规则,进行了二次调用,第一次的值作废。
2. `useEffect(() => /* */, [])`执行一此后,以新的严格模式的规则,调用了 `destructor` 后,进行了二次调用。
在第 2 点中,两次 useEffect 都是使用同一个值,是因为严格模式的二次调用按钩子分别执行两次,所以 useMemo 两次的调用都完毕后,得到的值再被 useEffect 执行两次。我调整了一下代码,将测试代码复制了一份在后面,可以看到 “useMemo” 和 “useMemo 2” 先执行了一次,又再执行了一次,然后再到 “useEffect“ 和 “useEffect 2"
![加倍快乐](https://minio.ivanli.cc/ivan-public/uPic/2023/OwlDG6.png)
## 结论
`useEffect` 应该独自管理副作用,要做到自己创建,自己销毁。
```tsx
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 不规范呢?(狗头