【发布时间】:2019-05-15 20:05:31
【问题描述】:
根据反应文档,useEffect 将在重新运行useEffect 部分之前触发清理逻辑。
如果你的效果返回一个函数,React 会在需要清理的时候运行它...
没有处理更新的特殊代码,因为
useEffect默认处理它们。它会在应用下一个效果之前清理之前的效果...
但是,当我在useEffect 中使用requestAnimationFrame 和cancelAnimationFrame 时,我发现cancelAnimationFrame 可能无法正常停止动画。有时,我发现旧动画仍然存在,而下一个效果带来另一个动画,这导致我的 Web 应用程序性能问题(尤其是当我需要渲染大量 DOM 元素时)。
我不知道 react 钩子在执行清理代码之前是否会做一些额外的事情,这使得我的取消动画部分不能正常工作,useEffect 钩子会做一些类似于闭包的事情来锁定状态变量?
useEffect 的执行顺序和内部清理逻辑是什么?是不是我下面写的代码有问题,导致cancelAnimationFrame不能完美运行?
谢谢。
//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>
【问题讨论】:
-
您是否尝试对类组件做同样的事情?结果不同吗?
-
useEffect(() => { setStartSeconds(Math.random());你在创建效果时导致了更新,我很惊讶你没有得到一个无限循环,我认为 React 在这里保护你。 -
@Keith 他有一个空数组作为
useEffect调用的第二个参数,这使得它只在挂载和卸载时执行。该块只是初始化和清理。话虽如此,他本可以使用该值初始化useState调用。 -
@KyleRichardson 但是他的第二个效果有
[startSeconds],所以无缘无故地有一个额外的渲染,正如他应该做的那样 ->const [startSeconds, setStartSeconds] = useState(Math.random());这可能不是问题的原因,但是对我来说,在效果初始化中改变状态感觉不对。 -
@Keith Ya 我知道,但这不是整个问题的原因。我同意这是不需要的重新渲染,应该在使用状态下初始化;但他遇到的问题是因为他在应该使用
useLayoutEffect时使用useEffect。
标签: javascript reactjs frontend react-hooks