V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
IvanLi127
V2EX  ›  React

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

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

    我知道 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  
       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   ·   实用小工具   ·   2559 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 15:41 · PVG 23:41 · LAX 07:41 · JFK 10:41
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.