关于 React 18 新的严格模式,我好像遇到了奇怪的问题

2023-02-06 14:08:56 +08:00
 IvanLi127

我知道 useEffect(() => {}, []) 会在严格开发模式下执行两次,但是,有传 deps 居然也会!

排查了半天发现是 React 18 改了 Stricter Strict Mode ,会首先装载一次组件,然后立即模拟卸载然后再装载,恢复到第一次状态的状态

然后我测试了一下,好像我并没有理解他说恢复第一次状态是什么意思。下图是运行结果。 红色是第一次装载,绿色是第二次装载,但是看蓝色所指的界面,后续的值是用第二次装载的值,那 React 所说的恢复到第一次的状态是指什么?

React 18 代码:CodeSandbox

如果不能恢复回第一次的状态的话,那我第二次的对象被销毁,就不可用了,而第一次的对象没销毁不就内存泄露了?所以我现在怀疑我代码是不是有问题。

正常来说在 useEffect 里创建资源并在返回的函数里销毁资源是最好的,不过 React 18 的表现似乎有点太超乎我的预期了,请求大佬指点

3325 次点击
所在节点    React
20 条回复
zed1018
2023-02-06 14:13:57 +08:00
useEffect 根据官方文档,就是会调用两次,useEffect 不是你想要的生命周期函数,如果你想要 class 那种生命周期函数,应该考虑使用 react-use 之类的第三方 hooks api 去做
sakae010
2023-02-06 14:22:22 +08:00
开发模式下就是会调 2 次,官网文档有说,是因为严格模式,去掉就没了,生产模式没有这个问题,这是官方的回答:
1.这是 React18 才新增的特性。
2.仅在开发模式("development")下,且使用了严格模式("Strict Mode")下会触发。
生产环境("production")模式下和原来一样,仅执行一次。
3.之所以执行两次,是为了模拟立即卸载组件和重新挂载组件。
为了帮助开发者提前发现重复挂载造成的 Bug 的代码。
同时,也是为了以后 React 的新功能做铺垫。
未来会给 React 增加一个特性,允许 React 在保留状态的同时,能够做到仅仅对 UI 部分的添加和删除。
让开发者能够提前习惯和适应,做到组件的卸载和重新挂载之后, 重复执行 useEffect 的时候不会影响应用正常运行
wu67
2023-02-06 14:23:20 +08:00
印象中我之前在玩的时候, 是直接关掉了严格模式......
不清楚开着严格模式要怎么处理
IvanLi127
2023-02-06 14:33:47 +08:00
@zed1018
@sakae010
谢谢两位解答,执行两次这个没啥问题,之前我和严格模式相处也挺好的。

主要是 v18 后,他无论是否有传递依赖项都会执行两次时,这个也还好,就是有个问题,我的 useEffect 里,他两次执行的都是同一个对象,这就导致我本来正常要使用的对象被手动释放掉了,我就傻掉了:

```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]);
```
IvanLi127
2023-02-06 14:37:36 +08:00
@wu67 开着严格模式的话,保证 hooks 里都是纯函数就好了,useEffect 这类得由自己创建有副作用的资源,再由自己的解构函数释放就好了。

我这问题最后是这么解决的,我就为了代码好看那么一点点才这样写的,结果在 v18 翻车了。

我不明白为啥表现好像和官方文档描述不太一样,就提了个问题。
MossFox
2023-02-06 14:38:59 +08:00
@IvanLi127 是这样的,这算是有意这么设计的。设想一下如果这个对象的生命周期如果应该和当前 component 一致、且会涉及到释放操作,那么初始化和回收都应该交给 useEffect 去做。
momocraft
2023-02-06 14:42:08 +08:00
首先 React 只是一个 JS 库, 他能恢复的只有自己接触得到的状态

我们按 log 里的行号顺序朝下看

3. render 时
4. render 时
5. commit 后, 开始 effect1
6. 立刻停止 effect1 ( **你的** effect1 destructor 应该在此时清理现场, 尽量恢复到没有发生过 effect1 的状态)
7. 开始 effect2 (我理解 "恢复" 是说这里 react 保证 effect2 看到的东西和 effect1 一样)
MossFox
2023-02-06 14:42:18 +08:00
可以写两个 useEffect ,一个只负责初始化和释放对象,另一个负责在存储这个对象的 state 完成初始化时再开始执行。
上面的 controls 这个变量就会需要从 useMemo 换成 useState 。
momocraft
2023-02-06 14:44:56 +08:00
camera 既然不是在 effect 里创建的, 就不应该在 effect 结束时回收
StrictMode 的这个行为就是为了暴露这样的问题
TWorldIsNButThis
2023-02-06 14:45:40 +08:00
意思就是如果某个 effect 只要运行一次就用 ref 做个标记严格按照标记判断要不要运行,而不是依赖 useeffect 本身的特性
IvanLi127
2023-02-06 14:57:14 +08:00
@TWorldIsNButThis #10 我一直觉得用 ref 标记比较恶心,很少用这种方法,一般都能找到 deps 传给第二个参数,就能避免重复,看来 v18 就不能这么玩了。
momocraft
2023-02-06 14:57:26 +08:00
"恢复" 也可能是说 #7 第 6 行让你的 destructor 有机会清理现场
MossFox
2023-02-06 14:58:24 +08:00
补充一下,如果确实就希望这个对象被创建一次,可以参考新版文档的这个例子:
https://beta.reactjs.org/learn/you-might-not-need-an-effect#initializing-the-application

更准确地说,就是如果非常确定一个对象的生命周期不是与组件绑定、而是与整个应用绑定,那么就可以不用 React 的状态管理、并且也最好别用。

(另外,前面提到的 useEffect chain 可能不会很合适,毕竟带来了额外的 rerender)
IvanLi127
2023-02-06 15:12:31 +08:00
@momocraft #7, #12

不过我看日志打出来 effect1 和 effect2 其实都是用第二个 memoValue 进去的,是不是因为第一个 memoValue 被 React 节流掉了,然后这两个 effect 是由严格模式执行两次产生的?

#9 我一直以为 StrictMode 是帮助我排除副作用的隐患,没想到还和这个有关系。
TWorldIsNButThis
2023-02-06 16:13:57 +08:00
@IvanLi127 可以写一个 hook ,useMounted 啥的
然后 memo 其实 react 也并不保证只在 deps 变化的时候运行,而是只保证 deps 变化的时候一定运行
hhacker
2023-02-06 16:19:48 +08:00
React 新人表示最初遇到这个 useEffect 在严格模式下被执行两次的特性的时候还挺震惊的,但是之后,适应了就还挺享收的,很多错误的用法都被提示了。
fancy967
2023-02-07 10:36:55 +08:00
翻阅过文档后+我个人的理解:
在 strict mode 下,double invoking 会发生在两种情况下:
1. Detecting unexpected side effects:针对 Function component bodies 和 useMemo ,但没有 useEffect ,对应到你图中红色和绿色的线
2. Ensuring reusable state ,也是 v18 新增的:针对 useEffect ,所谓的恢复到第一次的状态也只在这种情况下,因为 useEffect 两次调用的状态都是一样的
ccyu220
2023-02-07 11:59:25 +08:00
建议生命周期直接用 ahooks 代替,减少心智负担
Elephant696
2023-03-13 18:56:09 +08:00
react 可以不要维护了吗,除了 jsx ,我真是感受不到有什么优点,用 react 写个项目要累死。经常会出现炫了一堆技只为实现一个简单的功能。还有 react 推崇者经常说 react api 少,生态繁荣啥的。react 是少了,可他周边的配套的 api 没少到哪里去,甚至乱七八糟。hooks 各种心智问题、css 方案没有一个优秀的、router 难用的要死(现在比以前的好点)、状态管理库那真是割据混战、
CokeMine
2023-04-25 21:47:05 +08:00
ahooks 和 react-use 的 useMount 实现都是 useEffect(effect, [])。也没有对严格模式下这一行为进行规避(有搜到 PR 但是并没有想要合的意思)

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/913595

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX