useLayoutEffect深层剖析

useLayoutEffect

使用

useLayoutEffect 函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。

可以使用它来读取 DOM 布局并同步触发重渲染。

在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。

尽可能使用标准的 useLayoutEffect 以避免阻塞视觉更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { useState, useEffect, useLayoutEffect } from 'react';
export default function Demo() {
let [num, setNum] = useState(0);
// 再试试useLayoutEffect
useEffect(() => {
if (num === 0) {
let random = +String(Math.random()).substring(2);
setNum(random);
}
}, [num]);
return (
<div
style={{
background: 'lightblue',
WebkitUserSelect: 'none',
}}
onClick={() => {
setNum(0);
}}
>
{num}
</div>
);
}

useEffect 和 useLayoutEffect 区别

useLayoutEffect 会阻塞浏览器渲染真实 DOM【真实 DOM 对象已经创建了】,优先执行 Effect 链表中的 callback;

useEffect 不会阻塞浏览器渲染真实 DOM,在渲染真实 DOM 的同时,去执行 Effect 链表中的 callback

  • 它们里面的回调函数都是放在 effect 链表中的,但是 useLayoutEffect 设置的 callback 要优先于 useEffect 去执行
  • 在两者设置的 callback 中,依然可以获取 DOM 元素「原因:真实 DOM 对象已经创建了,区别只是浏览器是否渲染」
  • 如果在 callback 函数中又修改了状态值「视图又要更新」
    • useEffect:浏览器肯定是把第一次的真实已经绘制了,再去渲染第二次真实 DOM【频繁切换有闪烁】
    • useLayoutEffect:浏览器是把两次真实 DOM 的渲染,合并在一起渲染的【频繁切换无闪烁】

视图更新周期:

第一步:基于 babel-preset-react-app 把 JSX 编译为 createElement 格式

第二步:执行 createElement(…)方法,创建出 virtualDOM

第三步:基于 root.render 方法把 virtualDOM 变为真实 DOM 对象「DOM-DIFF」

useLayoutEffect 阻塞浏览器绘制:在整个视图渲染更新周期中,创建出真实 DOM 以后直接执行 useLayoutEffect 的 effect 链表中的方法。如果该方法有 setXXX 操作,那么会直接进入下一次更新周期中,而不会执行第四步。因此无论视图更新的过程执行了多少次,界面永远只看到了一次变化,即【频繁切换无闪烁】

useEffect 不阻塞浏览器绘制:在 React 渲染完真实 DOM 之后、浏览器绘制完毕之前会执行 effect 链表中的方法,同时第四步也会异步执行。如果 effect 链表中的方法有 setXXX 操作,那么会直接进入下一次更新周期中,同时上一次更新周期中的第四步还在执行,浏览器还在重绘。如果在 useEffect 中频繁触发更新,后台会异步运行多个“第四步”,由于浏览器绘制是需要一定时间的,因此对于速度较慢的设备,用户会看到多次重绘之间的“白屏”,即【频繁切换有闪烁】

第四步:浏览器渲染和绘制真实 DOM 对象

从视图更新周期可以看出,useLayoutEffect 和 useEffect 都是可以获取真实 DOM 的时机

官方文档示例参考

https://react.docschina.org/reference/react/useLayoutEffect#usage


useLayoutEffect深层剖析
https://hugtyftg.github.io/2023/06/17/useLayoutEffect/
作者
mmy@hugtyftg
发布于
2023年6月17日
许可协议