提出一个问题:useLayoutEffect是渲染前执行的。那么下面的代码里,testA > 0.5时能获取到最新的dom吗?
分析:
首先点击了button ,testA变为大于0.5的数字,
此时进入useLayoutEffect,如果按照上述的理解,useLayoutEffect是渲染前执行的,
那么此时虽然 testA > 0.5 但是redDiv是还没来得及渲染出来呢!
就不能获取到真实dom。
先看下面代码:
function ChildFunction() {
const [testA, setTestA] = useState(0);
useLayoutEffect(() => {
console.log('useLayoutEffect', testA);
if (testA > 0.5) {
setTestA(0.5);
}
const redDiv = document.querySelector('#redDiv');
console.log('useLayoutEffect-----redDiv', redDiv?.offsetTop);
}, [testA]);
return (
<div>
ChildFunction 组件
<br />
<button
type="button"
onClick={() => {
setTestA(Math.random() * 100);
}}
>
click
</button>
{testA > 0.5 ? (
<div
id="redDiv"
style={{
backgroundColor: 'red',
width: '300px',
height: '300px',
margin: '200px',
}}
></div>
) : null}
</div>
);
}
输出:
如果按照大多数资料里的意思,useLayoutEffect 是渲染前执行的,怎么可能获取到 真实dom(id=redDiv 的div)呢?
原因:
先来理解页面渲染的步骤
首先我们得明白浏览器渲染的几个步骤 :
dom tree - cssom tree - 前2者合并 生成render tree - 布局 layout - 绘制(Paint)- 合成(Composite)
这里解释一下后3个概念
布局(Layout):浏览器根据渲染树中每个节点的大小、位置和样式等信息,计算出每个节点在屏幕上的精确位置和大小,并生成布局信息。
绘制(Paint):浏览器根据布局信息,使用图形库将每个节点绘制成图像,并填充颜色、渐变、阴影等效果。
合成(Composite):浏览器将所有绘制的图像按照正确的顺序合成到屏幕上。
useLayoutEffect 是在组件渲染过程中调用,在浏览器 layout 的时候同步执行的Hook。
这也就是useLayoutEffect名称的来源!
也就是说,useLayoutEffect可以同步地读取布局信息。可以理解为 useLayoutEffect执行的时机是真实dom已经准备好了,就是没画到页面上。
所以:不是渲染前调用,是渲染完成之前调用!!
useLayoutEffect的作用
简单说就是:在用户看到前,改变某个元素的位置,可防止闪烁。
普通情况下,使用useEffect人眼是看不出闪烁的。
下面的例子能明显看出,当有一些长任务阻塞渲染的时候,才会导致出现闪烁的问题。
但是,此时若使用了 useLayoutEffect 其实也会导致页面很久才能响应。所以此时应该:拆分长任务、或使用useTransition 来提高响应速度 同时 使用useLayoutEffect以防出现闪烁。
useLayoutEffect Vs 快照 Vs useEffect
代码如下,isClick 默认是 false
结果如下,getSnapShotBeforeUpdate是无法获取最新的dom元素的!!ref 跟 document.getxxx操作都是去获取真实dom!!
总结:
useEffect: 渲染完后,才执行。所以不阻塞渲染。能拿到最新的dom
useLayoutEffect: 渲染到layout后,执行。会阻塞渲染。能拿到最新的dom结构。
getSnapShotBeforeUpdate: render后,渲染前执行。不阻塞渲染。拿不到最新的dom,能拿到旧的dom。这个函数的目的就是去取旧的dom.
执行顺序:render方法 - getSnapShotBeforeUpdate - 渲(useLayoutEffect)染 - useEffect