根据反应文件,useEffect它将在重新运行useEffect部分之前触发清理逻辑。
useEffect
如果您的效果返回一个函数,React将在需要清理时运行它。 没有处理更新的特殊代码,因为useEffect默认情况下会处理更新。它会在应用下一个效果之前清理上一个效果…
如果您的效果返回一个函数,React将在需要清理时运行它。
没有处理更新的特殊代码,因为useEffect默认情况下会处理更新。它会在应用下一个效果之前清理上一个效果…
但是,当我在requestAnimationFrame和cancelAnimationFrame内部使用时useEffect,我发现cancelAnimationFrame可能无法正常停止动画。有时,我发现旧动画仍然存在,而下一个效果带来了另一个动画,这导致了我的Web应用程序性能问题(尤其是当我需要渲染沉重的DOM元素时)。
requestAnimationFrame
cancelAnimationFrame
我不知道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(() => { 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。
loopRaf
进一步的测试表明,该useLayoutEffect操作始终在之前被调用,requestAnimationFrame并且其清理功能在下一次执行之前被调用,以防止重叠。
useLayoutEffect
更改useEffect为useLayoutEffect,它应该可以解决您的问题。
useEffect并useLayoutEffect按照它们在代码中出现的顺序调用,就像useState调用一样。
useState
您可以通过运行以下几行来查看:
useEffect(() => { console.log('useEffect-1') }) useEffect(() => { console.log('useEffect-2') }) useLayoutEffect(() => { console.log('useLayoutEffect-1') }) useLayoutEffect(() => { console.log('useLayoutEffect-2') })