IvanLi127
V2EX  ›  React

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

  •  
  •   IvanLi127 ·
    IvanLi-CN · Feb 6, 2023 · 5746 views
    This topic created in 1373 days ago, the information mentioned may be changed or developed.

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

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

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

    React 18 代码:CodeSandbox

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

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

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

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

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

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

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

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

    (另外,前面提到的 useEffect chain 可能不会很合适,毕竟带来了额外的 rerender)
    IvanLi127
    IvanLi127
    OP
    Feb 6, 2023
    14
    @momocraft #7, #12

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

    #9 我一直以为 StrictMode 是帮助我排除副作用的隐患,没想到还和这个有关系。
    TWorldIsNButThis
    TWorldIsNButThis
    Feb 6, 2023❤️2
    15
    @IvanLi127 可以写一个 hook ,useMounted 啥的
    然后 memo 其实 react 也并不保证只在 deps 变化的时候运行,而是只保证 deps 变化的时候一定运行
    hhacker
    hhacker
    PRO
    Feb 6, 2023
    16
    React 新人表示最初遇到这个 useEffect 在严格模式下被执行两次的特性的时候还挺震惊的,但是之后,适应了就还挺享收的,很多错误的用法都被提示了。
    fancy967
    fancy967
    Feb 7, 2023❤️1
    17
    翻阅过文档后+我个人的理解:
    在 strict mode 下,double invoking 会发生在两种情况下:
    1. Detecting unexpected side effects:针对 Function component bodies 和 useMemo ,但没有 useEffect ,对应到你图中红色和绿色的线
    2. Ensuring reusable state ,也是 v18 新增的:针对 useEffect ,所谓的恢复到第一次的状态也只在这种情况下,因为 useEffect 两次调用的状态都是一样的
    ccyu220
    ccyu220
    Feb 7, 2023
    18
    建议生命周期直接用 ahooks 代替,减少心智负担
    Elephant696
    Elephant696
    Mar 13, 2023
    19
    react 可以不要维护了吗,除了 jsx ,我真是感受不到有什么优点,用 react 写个项目要累死。经常会出现炫了一堆技只为实现一个简单的功能。还有 react 推崇者经常说 react api 少,生态繁荣啥的。react 是少了,可他周边的配套的 api 没少到哪里去,甚至乱七八糟。hooks 各种心智问题、css 方案没有一个优秀的、router 难用的要死(现在比以前的好点)、状态管理库那真是割据混战、
    CokeMine
    CokeMine
    Apr 25, 2023
    20
    ahooks 和 react-use 的 useMount 实现都是 useEffect(effect, [])。也没有对严格模式下这一行为进行规避(有搜到 PR 但是并没有想要合的意思)
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   2290 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 40ms · UTC 13:35 · PVG 21:35 · LAX 05:35 · JFK 08:35
    ♥ Do have faith in what you're doing.