关于 React 18 新的严格模式,我好像遇到了奇怪的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
IvanLi127
V2EX    React

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

  •  
  •   IvanLi127
    IvanLi-CN 2023-02-06 14:08:56 +08:00 4773 次点击
    这是一个创建于 1052 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

    React 18 代码:CodeSandbox

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

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

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

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

    我不明白为啥表现好像和官方文档描述不太一样,就提了个问题。
    MossFox
        6
    MossFox  
       2023-02-06 14:38:59 +08:00
    @IvanLi127 是这样的,这算是有意这么设计的。设想一下如果这个对象的生命周期如果应该和当前 component 一致、且会涉及到释放操作,那么初始化和回收都应该交给 useEffect 去做。
    momocraft
        7
    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
        8
    MossFox  
       2023-02-06 14:42:18 +08:00
    可以写两个 useEffect ,一个只负责初始化和释放对象,另一个负责在存储这个对象的 state 完成初始化时再开始执行。
    上面的 controls 这个变量就会需要从 useMemo 换成 useState 。
    momocraft
        9
    momocraft  
       2023-02-06 14:44:56 +08:00   1
    camera 既然不是在 effect 里创建的, 就不应该在 effect 结束时回收
    StrictMode 的这个行为就是为了暴露这样的问题
    TWorldIsNButThis
        10
    TWorldIsNButThis  
       2023-02-06 14:45:40 +08:00
    意思就是如果某个 effect 只要运行一次就用 ref 做个标记严格按照标记判断要不要运行,而不是依赖 useeffect 本身的特性
    IvanLi127
        11
    IvanLi127  
    OP
       2023-02-06 14:57:14 +08:00 via Android
    @TWorldIsNButThis #10 我一直觉得用 ref 标记比较恶心,很少用这种方法,一般都能找到 deps 传给第二个参数,就能避免重复,看来 v18 就不能这么玩了。
    momocraft
        12
    momocraft  
       2023-02-06 14:57:26 +08:00
    "恢复" 也可能是说 #7 第 6 行让你的 destructor 有机会清理现场
    MossFox
        13
    MossFox  
       2023-02-06 14:58:24 +08:00   2
    补充一下,如果确实就希望这个对象被创建一次,可以参考新版文档的这个例子:
    https://beta.reactjs.org/learn/you-might-not-need-an-effect#initializing-the-application

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

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

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

    #9 我一直以为 StrictMode 是帮助我排除副作用的隐患,没想到还和这个有关系。
    TWorldIsNButThis
        15
    TWorldIsNButThis  
       2023-02-06 16:13:57 +08:00   2
    @IvanLi127 可以写一个 hook ,useMounted 啥的
    然后 memo 其实 react 也并不保证只在 deps 变化的时候运行,而是只保证 deps 变化的时候一定运行
    hhacker
        16
    hhacker  
       2023-02-06 16:19:48 +08:00
    React 新人表示最初遇到这个 useEffect 在严格模式下被执行两次的特性的时候还挺震惊的,但是之后,适应了就还挺享收的,很多错误的用法都被提示了。
    fancy967
        17
    fancy967  
       2023-02-07 10:36:55 +08:00   1
    翻阅过文档后+我个人的理解:
    在 strict mode 下,double invoking 会发生在两种情况下:
    1. Detecting unexpected side effects:针对 Function component bodies 和 useMemo ,但没有 useEffect ,对应到你图中红色和绿色的线
    2. Ensuring reusable state ,也是 v18 新增的:针对 useEffect ,所谓的恢复到第一次的状态也只在这种情况下,因为 useEffect 两次调用的状态都是一样的
    ccyu220
        18
    ccyu220 &bsp;
       2023-02-07 11:59:25 +08:00
    建议生命周期直接用 ahooks 代替,减少心智负担
    Elephant696
        19
    Elephant696  
       2023-03-13 18:56:09 +08:00
    react 可以不要维护了吗,除了 jsx ,我真是感受不到有什么优点,用 react 写个项目要累死。经常会出现炫了一堆技只为实现一个简单的功能。还有 react 推崇者经常说 react api 少,生态繁荣啥的。react 是少了,可他周边的配套的 api 没少到哪里去,甚至乱七八糟。hooks 各种心智问题、css 方案没有一个优秀的、router 难用的要死(现在比以前的好点)、状态管理库那真是割据混战、
    CokeMine
        20
    CokeMine  
       2023-04-25 21:47:05 +08:00
    ahooks 和 react-use 的 useMount 实现都是 useEffect(effect, [])。也没有对严格模式下这一行为进行规避(有搜到 PR 但是并没有想要合的意思)
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3996 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 40ms UTC 05:22 PVG 13:22 LAX 21:22 JFK 00:22
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86