hij*_*tao 9 javascript frontend reactjs react-hooks
根据反应文件,useEffect在重新运行useEffect部分之前将触发清理逻辑.
如果你的效果返回一个函数,React将在清理时运行它...
没有用于处理更新的特殊代码,因为
useEffect默认情况下处理它们.它会在应用下一个效果之前清除之前的效果......
然而,当我使用requestAnimationFrame和cancelAnimationFrame里面useEffect,我发现cancelAnimationFrame可能无法正常停止动画.有时,我发现旧动画仍然存在,而下一个效果会带来另一个动画,这会导致我的Web应用程序性能问题(特别是当我需要渲染繁重的DOM元素时).
我不知道反应钩子在执行清理代码之前是否会做一些额外的事情,这使得我的取消动画部分不能正常工作,会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"));Run Code Online (Sandbox Code Playgroud)
<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>Run Code Online (Sandbox Code Playgroud)
Kyl*_*son 12
将这三行代码放在一个组件中,您将看到它们的优先级顺序。
useEffect(() => {
console.log('useEffect')
return () => {
console.log('useEffect cleanup')
}
})
window.requestAnimationFrame(() => console.log('requestAnimationFrame'))
useLayoutEffect(() => {
console.log('useLayoutEffect')
return () => {
console.log('useLayoutEffect cleanup')
}
})
Run Code Online (Sandbox Code Playgroud)
useLayoutEffect > requestAnimationFrame > useEffect
您遇到的问题是由于loopRaf在执行清除功能之前请求另一个动画帧引起的useEffect。
进一步的测试表明,useLayoutEffect它总是在之前被调用,requestAnimationFrame并且它的清理函数在下一次执行之前被调用,以防止重叠。
更改
useEffect为useLayoutEffect它应该可以解决您的问题。
useEffect并useLayoutEffect按照它们在代码中出现的顺序调用,就像useState调用一样。
您可以通过运行以下几行来查看这一点:
useEffect(() => {
console.log('useEffect-1')
})
useEffect(() => {
console.log('useEffect-2')
})
useLayoutEffect(() => {
console.log('useLayoutEffect-1')
})
useLayoutEffect(() => {
console.log('useLayoutEffect-2')
})
Run Code Online (Sandbox Code Playgroud)
在使用钩子和尝试实现生命周期功能时,您需要关注两种不同的钩子。
根据文档:
useEffect在 react 渲染您的组件后运行,并确保您的效果回调不会阻止浏览器绘制。这不同于类组件的行为,其中componentDidMount和componentDidUpdate渲染后同步运行。
因此requestAnimationFrame在这些生命周期中使用可以无缝地工作,但在useEffect. 因此,当您必须进行的更改不会阻止视觉更新(例如在收到响应后进行导致 DOM 更改的 API 调用)时,应该使用 useEffect。
另一个不太流行但在处理可视化 DOM 更新时非常方便的钩子是useLayoutEffect. 根据文档
签名与 useEffect 相同,但在所有 DOM 突变后同步触发。使用它从 DOM 读取布局并同步重新渲染。
useLayoutEffect在浏览器有机会绘制之前,内部计划的更新将同步刷新。
因此,如果您的效果正在改变 DOM(通过 DOM 节点引用)并且 DOM 突变将在渲染它和您的效果改变它之间改变 DOM 节点的外观,那么您不想使用useEffect. 你会想要使用useLayoutEffect. 否则,当您的 DOM 更改生效时,用户可能会看到闪烁,这正是requestAnimationFrame
//import React, { useState, useEffect } from "react";
const {useState, useLayoutEffect} = React;
//import ReactDOM from "react-dom";
function App() {
const [startSeconds, setStartSeconds] = useState("");
const [progress, setProgress] = useState(0);
useLayoutEffect(() => {
setStartSeconds(Math.random());
const interval = setInterval(() => {
setStartSeconds(Math.random());
}, 1000);
return () => clearInterval(interval);
}, []);
useLayoutEffect(
() => {
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"));Run Code Online (Sandbox Code Playgroud)
<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>Run Code Online (Sandbox Code Playgroud)
在上面的答案中还不清楚的一件事是,当您混合使用多个成分时,效果的运行顺序。我们一直在进行涉及通过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
Run Code Online (Sandbox Code Playgroud)
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 <></>
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
4794 次 |
| 最近记录: |