什么是 useEffect 执行顺序及其在 React 钩子中的内部清理逻辑? [英] What's useEffect execution order and its internal clean-up logic in react hooks?

查看:29
本文介绍了什么是 useEffect 执行顺序及其在 React 钩子中的内部清理逻辑?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

根据react文档,useEffect会在重新运行useEffect部分之前触发清理逻辑.

<块引用>

如果你的 effect 返回一个函数,React 会在需要清理时运行它......

没有用于处理更新的特殊代码,因为 useEffect 默认处理它们.它会在应用下一个效果之前清除之前的效果...

但是,当我在useEffect 中使用requestAnimationFramecancelAnimationFrame 时,我发现cancelAnimationFrame 可能无法正常停止动画.有时,我发现旧的动画仍然存在,而下一个效果带来了另一个动画,这会导致我的 Web 应用程序性能问题(尤其是当我需要渲染大量 DOM 元素时).

不知道react hook会不会在执行清理代码之前做一些额外的事情,导致我的cancel-animation部分不能正常工作,useEffect hook会不会做一些类似closure的事情锁定状态变量?

useEffect 的执行顺序和内部清理逻辑是什么?是不是我下面写的代码有问题,导致cancelAnimationFrame不能完美运行?

谢谢.

//import React, { useState, useEffect } from "react";const {useState, useEffect} = React;//从react-dom"导入ReactDOM;功能应用(){const [startSeconds, setStartSeconds] = useState(Math.random());const [progress, setProgress] = useState(0);useEffect(() => {const 间隔 = setInterval(() => {setStartSeconds(Math.random());}, 1000);返回 () =>清除间隔(间隔);}, []);使用效果(() =>{让 raf = 空;const onFrame = () =>{const currentProgress = startSeconds/120.0;setProgress(Math.random());//console.log(currentProgress);loopRaf();如果(当前进度 > 100){停止拉夫();}};const loopRaf = () =>{raf = window.requestAnimationFrame(onFrame);//console.log('分配的 Raf ID: ', raf);};const stopRaf = () =>{console.log("停止", raf);window.cancelAnimationFrame(raf);};loopRaf();返回 () =>{console.log("已清理的 Raf ID:", raf);//console.log('init', raf);//setTimeout(() => console.log("500ms later", raf), 500);//setTimeout(()=> console.log('5s later', raf), 5000);停止拉夫();};},[开始秒]);让 t = [];for (让 i = 0; i <1000; i++) {t.push(i);}返回 (<div className="应用程序"><h1>Hello CodeSandbox</h1><text>{progress}</text>{t.map(e => (<span>{progress}</span>))}

);}ReactDOM.render(,document.querySelector("#root"));

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.7.0-alpha.2/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.7.0-alpha.2/umd/react-dom.production.min.js"></script><div id="root"></div>

解决方案

将这三行代码放在一个组件中,您将看到它们的优先级顺序.

 useEffect(() => {console.log('useEffect')返回 () =>{console.log('useEffect 清理')}})window.requestAnimationFrame(() => console.log('requestAnimationFrame'))useLayoutEffect(() => {console.log('useLayoutEffect')返回 () =>{console.log('useLayoutEffect 清理')}})

useLayoutEffect >requestAnimationFrame >使用效果

您遇到的问题是由 loopRaf 在执行 useEffect 的清理函数之前请求另一个动画帧引起的.

进一步的测试表明,useLayoutEffect 总是在 requestAnimationFrame 之前被调用,并且它的清理函数在下一次执行之前被调用,以防止重叠.

<块引用>

useEffect 更改为 useLayoutEffect,它应该可以解决您的问题问题.

useEffectuseLayoutEffect 按照它们在代码中出现的顺序调用,就像 useState 调用一样.

您可以通过运行以下几行来查看:

 useEffect(() => {console.log('useEffect-1')})useEffect(() => {console.log('useEffect-2')})useLayoutEffect(() => {console.log('useLayoutEffect-1')})useLayoutEffect(() => {console.log('useLayoutEffect-2')})

According to react document, useEffect will trigger clean-up logic before it re-runs useEffect part.

If your effect returns a function, React will run it when it is time to clean up...

There is no special code for handling updates because useEffect handles them by default. It cleans up the previous effects before applying the next effects...

However, when I use requestAnimationFrame and cancelAnimationFrame inside useEffect, I found the cancelAnimationFrame may not stop the animation normally. Sometimes, I found the old animation still exists, while the next effect brings another animation, which causes my web app performance issues (especially when I need to render heavy DOM elements).

I don't know whether react hook will do some extra things before it executes the clean-up code, which make my cancel-animation part not work well, will useEffect hook do something like closure to lock the state variable?

What's useEffect's execution order and its internal clean-up logic? Is there something wrong the code I write below, which makes cancelAnimationFrame can't work perfectly?

Thanks.

//import React, { useState, useEffect } from "react";

const {useState, useEffect} = React;

//import ReactDOM from "react-dom";

function App() {
  const [startSeconds, setStartSeconds] = useState(Math.random());
  const [progress, setProgress] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setStartSeconds(Math.random());
    }, 1000);

    return () => clearInterval(interval);
  }, []);

  useEffect(
    () => {
      let raf = null;

      const onFrame = () => {
        const currentProgress = startSeconds / 120.0;
        setProgress(Math.random());
        // console.log(currentProgress);
        loopRaf();
        if (currentProgress > 100) {
          stopRaf();
        }
      };

      const loopRaf = () => {
        raf = window.requestAnimationFrame(onFrame);
        // console.log('Assigned Raf ID: ', raf);
      };

      const stopRaf = () => {
        console.log("stopped", raf);
        window.cancelAnimationFrame(raf);
      };

      loopRaf();

      return () => {
        console.log("Cleaned Raf ID: ", raf);
        // console.log('init', raf);
        // setTimeout(() => console.log("500ms later", raf), 500);
        // setTimeout(()=> console.log('5s later', raf), 5000);
        stopRaf();
      };
    },
    [startSeconds]
  );

  let t = [];
  for (let i = 0; i < 1000; i++) {
    t.push(i);
  }

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <text>{progress}</text>
      {t.map(e => (
        <span>{progress}</span>
      ))}
    </div>
  );
}

ReactDOM.render(<App />,
document.querySelector("#root"));

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.7.0-alpha.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.7.0-alpha.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>

解决方案

Put these three lines of code in a component and you'll see their order of priority.

  useEffect(() => {
    console.log('useEffect')
    return () => {
      console.log('useEffect cleanup')
    }
  })

  window.requestAnimationFrame(() => console.log('requestAnimationFrame'))

  useLayoutEffect(() => {
    console.log('useLayoutEffect')
    return () => {
      console.log('useLayoutEffect cleanup')
    }
  })

useLayoutEffect > requestAnimationFrame > useEffect

The problem you're experiencing is caused by loopRaf requesting another animation frame before the cleanup function for useEffect is executed.

Further testing has shown that useLayoutEffect is always called before requestAnimationFrame and that its cleanup function is called before the next execution preventing overlaps.

Change useEffect to useLayoutEffect and it should solve your problem.

useEffect and useLayoutEffect are called in the order they appear in your code for like types just like useState calls.

You can see this by running the following lines:

  useEffect(() => {
    console.log('useEffect-1')
  })
  useEffect(() => {
    console.log('useEffect-2')
  })
  useLayoutEffect(() => {
    console.log('useLayoutEffect-1')
  })
  useLayoutEffect(() => {
    console.log('useLayoutEffect-2')
  })

这篇关于什么是 useEffect 执行顺序及其在 React 钩子中的内部清理逻辑?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆