react挂钩中的useEffect执行顺序及其内部清理逻辑是什么? [英] What's useEffect execution order and its internal clean-up logic in react hooks?
问题描述
根据反应文档,useEffect
将在重新运行useEffect
部分之前触发清理逻辑.
According to react document, useEffect
will trigger clean-up logic before it re-runs useEffect
part.
如果您的效果返回了一个函数,React会在需要清理时运行它...
If your effect returns a function, React will run it when it is time to clean up...
没有特殊的代码来处理更新,因为默认情况下useEffect
会处理它们.在应用下一个效果之前,它会清理之前的效果...
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...
但是,当我在useEffect
中使用requestAnimationFrame
和cancelAnimationFrame
时,我发现cancelAnimationFrame可能无法正常停止动画.有时,我发现旧动画仍然存在,而下一个效果带来了另一个动画,这导致了我的Web应用程序性能问题(尤其是当我需要渲染沉重的DOM元素时).
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).
我不知道React钩子是否会在执行清理代码之前做一些额外的事情,这会使我的取消动画部分无法正常工作,useEffect
钩子会做类似闭包的操作来锁定状态变量?
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?
useEffect的执行顺序及其内部清理逻辑是什么?我在下面编写的代码有什么问题,这使cancelAnimationFrame无法完美运行吗?
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?
谢谢.
//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
您遇到的问题是由loopRaf
在执行useEffect
的清除功能之前请求另一个动画帧引起的.
The problem you're experiencing is caused by loopRaf
requesting another animation frame before the cleanup function for useEffect
is executed.
进一步的测试表明,useLayoutEffect
总是在requestAnimationFrame
之前被调用,并且其清理函数在下一次执行之前被调用以防止重叠.
Further testing has shown that useLayoutEffect
is always called before requestAnimationFrame
and that its cleanup function is called before the next execution preventing overlaps.
将
useEffect
更改为useLayoutEffect
,它应该可以解决您的问题 问题.
Change
useEffect
touseLayoutEffect
and it should solve your problem.
useEffect
和useLayoutEffect
按照它们在代码中出现的顺序被调用,就像useState
调用一样.
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')
})
这篇关于react挂钩中的useEffect执行顺序及其内部清理逻辑是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!