1.背景
面试官问到:请说明下
useEffect
和useLayoutEffect
的区别。我:一般开发场景都是用的
useEffect
,react官方文档上说,两者的使用场景稍微有些不同,但是还是推荐尽量使用useEffect
。面试官:不够细节,再说点,摩多摩多。
我:...
2.官网介绍
对比 | useEffect | useLayoutEffect |
---|---|---|
简介 | useEffect 是一个 React Hook,它允许你 将组件与外部系统同步。 |
useLayoutEffect 是 useEffect 的一个版本,在浏览器重新绘制屏幕之前触发。 |
aip | useEffect(setup, dependencies?) |
useLayoutEffect(setup, dependencies?) |
返回值 | undefined | undefined |
3. 对比总结
3.1 useEffect
3.1.1 依赖项对effect执行的影响
-
无依赖项:没有依赖项数组:每次重新渲染后重新运行!
-
错误依赖项:
-
依赖项每次渲染都不同:指定了依赖项数组,你的 Effect 仍循环地重新运行
- => 解决:手动打印依赖项数组进行排查:
console.log([dep1, dep2]);
- => 解决:手动打印依赖项数组进行排查:
-
3.1.2 运行闪烁
我的 Effect 做了一些视觉相关的事情,在它运行之前我看到了一个闪烁
-
产生原因:
useEffect
是异步宏任务,在下一轮事件循环才会执行。而GUI渲染和JS线程是互斥的,也就是说,在Effect 运行前,浏览器就已经进行了屏幕渲染,渲染完毕后,useEffect
才会被任务队列取出来,开始执行。 -
解决方法:
- 1.在得到正确的数据之前,使用loading
- 2.使用
useLayoutEffect
3.2 useLayoutEffect
3.2.1 依赖项对effect执行的影响
同
useEffect
3.2.2 运行闪烁
useLayoutEffect
是同步任务,也就是说,他会在GUI渲染之前触发。
useLayoutEffect
内部的代码和所有计划的状态更新阻塞了浏览器重新绘制屏幕。如果过度使用,这会使你的应用程序变慢。如果可能的话,尽量选择useEffect
-
使用场景:
- 在浏览器重绘前计算布局(官网以toolTip组件为例)
scss
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0); // 你还不知道真正的高度
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height); // 现在重新渲染,你知道了真实的高度
}, []);
// ... 在下方的渲染逻辑中使用 tooltipHeight ...
}
4. 一个小demo
准备了一个小demo,可以直观的体现出二者的区别
4.1 部分关键代码:
scss
function App() {
const [num1, setNum1] = useState(0);
const [num2, setNum2] = useState(0);
useEffect(() => {
console.log("b");
setNum1(num1 + 1);
}, []);
useLayoutEffect(() => {
console.log("a");
setNum2(num2 + 1);
}, []);
useEffect(() => {
console.log(num1, num2);
}, [num1, num2]);
return (
<div className="App">
<header className="App-header">
{num1} -- {num2}
</header>
</div>
);
}
4.2 效果展示:
4.3 devtool打印结果:
浏览器设置如下:
- 在
chrome devtools
中性能节流
配置 cpu六倍降速
4.4 结果分析
5. 最后
nextTick
一般用于等待dom操作完成之后马上执行:等待弹窗打开后进行数据回显,这一点来说,与useEffect
的用途类似。非交互式的场景下,在执行逻辑不太多的情况下,可以使用useLayoutEffect
,避免闪烁,也避免页面白屏时间过长。