javascript - react 钩子中的 useEffect 执行顺序及其内部清理逻辑是什么?
问题描述
根据反应文档,将在重新运行部分useEffect
之前触发清理逻辑。useEffect
如果你的效果返回一个函数,React 会在需要清理的时候运行它......
没有用于处理更新的特殊代码,因为
useEffect
默认情况下会处理它们。它会在应用下一个效果之前清理以前的效果......
但是,当我使用requestAnimationFrame
and cancelAnimationFrame
inside时useEffect
,我发现 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>
解决方案
上述答案中不清楚的一件事是当您在混合中有多个组件时效果运行的顺序。我们一直在做涉及通过 useContext 在父级和子级之间进行协调的工作,因此顺序对我们来说更重要。useLayoutEffect
并useEffect
在这方面以不同的方式开展工作。
useEffect
在移动到下一个组件(深度优先)并执行相同操作之前运行清理和新效果。
useLayoutEffect
运行每个组件的清理(深度优先),然后运行所有组件的新效果(深度优先)。
render parent
render a
render b
layout cleanup a
layout cleanup b
layout cleanup parent
layout effect a
layout effect b
layout effect parent
effect cleanup a
effect a
effect cleanup b
effect b
effect cleanup parent
effect parent
const Test = (props) => {
const [s, setS] = useState(1)
console.log(`render ${props.name}`)
useEffect(() => {
const name = props.name
console.log(`effect ${props.name}`)
return () => console.log(`effect cleanup ${name}`)
})
useLayoutEffect(() => {
const name = props.name
console.log(`layout effect ${props.name}`)
return () => console.log(`layout cleanup ${name}`)
})
return (
<>
<button onClick={() => setS(s+1)}>update {s}</button>
<Child name="a" />
<Child name="b" />
</>
)
}
const Child = (props) => {
console.log(`render ${props.name}`)
useEffect(() => {
const name = props.name
console.log(`effect ${props.name}`)
return () => console.log(`effect cleanup ${name}`)
})
useLayoutEffect(() => {
const name = props.name
console.log(`layout effect ${props.name}`)
return () => console.log(`layout cleanup ${name}`)
})
return <></>
}
推荐阅读
- javascript - boostrap 3 + 数据表按钮:错位
- python - 根据索引从矩阵中删除一些元素
- powershell - 在 PowerShell 中移动、加密和存储 .txt 和 .pc 文件到 .pgp
- ionic-framework - 如何在电容器 v3 实时重载中查看 console.log 输出
- c++ - 结构不使用 cout 打印
- python - Centos 操作系统上不同进程的 UUID 保持不变,但在 Windows 操作系统上工作正常(每个进程流的 UUID)
- node.js - 无法在 M1 Mac 上使用 Docker 安装Sharp
- php - Binance Pay:PHP cURL 出现“此请求的签名无效”错误
- java - CorrectLogin() 处未处理的异常类型 IOException;
- flutter - GetX 状态管理的导航错误: