【react】useEffect与useLayoutEffect

useEffect与useLayoutEffect

相关文章

useEffect与useLayoutEffect


一、两者的区别

调用时机:

  • useEffect:useEffect 在组件渲染后(包括首次渲染和后续更新)异步执行,不会阻塞浏览器渲染。

  • useLayoutEffect:useLayoutEffect 会在所有 DOM 变更之后同步执行,但在浏览器绘制之前。因此,它的执行时机比 useEffect 更早,可能会阻塞浏览器的渲染。

    详细

    • 当元件被加入时 (mount),useEffect和useLayoutEffect 会被第一次执行。
    • 当每次元件重新渲染时,如果 dependencies 的值有改变,先将旧的 props 和 state 执行 cleanup function,再带着新- 的 props 和 state 执行 setup function。
    • cleanup function 的代码,会在元件生命周期结束 (unmount) 时,执行最后一次。

影响性能:

  • useEffect:由于 useEffect 是异步执行的,它不会阻塞浏览器的渲染,因此对于大多数副作用操作来说,它是更好的选择,可以避免影响页面的响应性能。
  • useLayoutEffect:因为 useLayoutEffect 是同步执行的,它可能会阻塞浏览器的渲染,因此在处理大量或计算密集型的操作时,可能会影响页面的性能表现。

适用场景:

  • useEffect:适用于大多数情况下,特别是涉及网络请求、数据订阅、DOM 操作等不需要同步执行的副作用操作。
  • useLayoutEffect:适用于需要在浏览器绘制之前立即执行的操作,比如需要测量 DOM 元素尺寸、获取布局信息等。通常情况下,应该谨慎使用 useLayoutEffect,确保不会影响页面的性能。

二、示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { useEffect, useLayoutEffect, useState } from "react";

export default function App() {
const [count, setCount] = useState(0);

useEffect(() => {
if (count === 0) {
const randomNum = 1 + Math.random() * 1000;
setCount(randomNum);
}
}, [count]);

return <div onClick={() => setCount(0)}>{count}</div>;
}

当我们执行上方代码时,连续点击 div 区块,会看到画面产生闪烁。

原因是,当你每次点击 div,此时 count 会被更新为 0,画面会重新渲染变为 0,同时,因为 count 被更新,也会触发 useEffect 执行。所以在重绘完成之后, useEffect 执行并把 count 更新为另一串随机数字,画面也会再渲染一次,因为两次渲染时间很快,所以造成闪烁。

假设我们把上方代码的useEffect 换成useLayoutEffect,当你每次点击 div,此时 count 会被更新为 0,但这时,画面不会被重新渲染变为 0,而是先等待 useLayoutEffect 内的代码执行完毕之后,state 已经更新为新的随机数字,这时画面才进行重绘。

三、如何dependencies为空,useEffect和useLayoutEffect 什么时候会执行?

此 effect 会是在每次重新渲染元件后重新执行

四、如果 dependencies 中有 object 会怎么样?

下方代码的 options 物件会在元件每次重新渲染时,都是不同的物件,所以这个 useEffect 可能会在每次渲染时都重新执行,因为在 dependencies 中的 options 有改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function ChatRoom({ articleId }) {
const [article, setArticle] = useState(null);

// options 会在每次渲染时重新创建
const options = {
serverUrl: 'https://localhost:1234',
articleId: articleId
};

useEffect(() => {
const data = getArticle(options);
setArticle(data)

// options 每次渲染时值都不同,因此触发 useEffect 执行
}, [options]);

如果要避免上方代码造成不必要触发 useEffect,其实可以把动态的物件放在 Effect 中,并把 dependencies 中的物件改为 string 或 number,如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
function ChatRoom({ articleId }) {
const [article, setArticle] = useState(null);

useEffect(() => {
const options = {
serverUrl: 'https://localhost:1234',
articleId: articleId
};

const data = getArticle(options);
setArticle(data)

}, [articleId]);

五、什么时候使用 useLayoutEffect?

当需要在浏览器绘制之前立即执行的操作时,比如需要测量 DOM 元素尺寸、获取布局信息等,可以使用 useLayoutEffect。以下是一些可能的示例:

获取元素尺寸

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { useLayoutEffect, useRef, useState } from 'react';

function Component() {
const [width, setWidth] = useState(0);
const elementRef = useRef();

useLayoutEffect(() => {
// 获取元素的宽度
const elementWidth = elementRef.current.clientWidth;
setWidth(elementWidth);
}, []); // 依赖项为空,仅在组件挂载时执行

return (
<div ref={elementRef}>
Width: {width}px
</div>
);
}

在这个例子中,我们有一个组件,它包含一个 div 元素,并且我们想要获取这个 div 元素的宽度。我们使用了 useLayoutEffect 来确保在元素被渲染到 DOM 后立即获取其尺寸,然后更新组件的状态以显示宽度。这样,我们就可以在浏览器绘制之前得到最新的布局信息。

如果改为useEffect,则会渲染两次,第一次宽度是0,第二次才是真实测量的值。

调整布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { useLayoutEffect, useState } from 'react';

function Component() {
const [isVisible, setIsVisible] = useState(false);

useLayoutEffect(() => {
// 在组件挂载或更新时,调整布局以确保元素可见
if (!isVisible) {
// 滚动到指定位置
window.scrollTo({
top: 0,
behavior: 'smooth'
});
}
}, [isVisible]); // 当 isVisible 发生变化时执行

return (
<div>
<button onClick={() => setIsVisible(true)}>Show</button>
{isVisible && <div style={{ height: '2000px' }}>Visible Content</div>}
</div>
);
}

在这个例子中,我们有一个组件,它包含一个按钮和一个条件渲染的元素。当点击按钮时,元素将变为可见状态。我们使用 useLayoutEffect 来确保在元素变为可见状态时,浏览器会立即滚动到页面顶部,以确保用户可以立即看到该元素。这样,我们就可以在浏览器绘制之前执行必要的布局调整操作。

如果改为useEffect,滚动条则会肉眼可见的滚动。


【react】useEffect与useLayoutEffect
https://www.cccccl.com/20240307/react/useEffect与useLayoutEffect/
作者
Jeffrey
发布于
2024年3月7日
许可协议