ByteNoteByteNote

字节笔记本

2026年2月17日

useEffect 和 useLayoutEffect 的区别详解

API中转
¥120

本文深入讲解 React 中 useEffect 和 useLayoutEffect 两个 Hook 的区别,通过实际代码示例帮助开发者理解何时使用哪个 Hook,避免常见的闪屏问题。

useEffect 和 useLayoutEffect 的区别

在 React 函数组件中,useEffect 和 useLayoutEffect 都是用于处理副作用的 Hook,但它们在执行时机上有重要区别。理解这个区别对于编写高性能、无视觉问题的 React 应用至关重要。

useEffect

基本特性

useEffect 是 React 中最常用的副作用 Hook,在 90% 的场景下都应该使用它。

执行时机:

  • 在 render 结束后执行
  • 不会阻塞浏览器绘制(paint)
  • 采用异步方式执行

与 Class 组件的对比:

  • useEffect 是异步的,在浏览器绘制后才执行
  • componentDidMount 和 componentDidUpdate 是同步的,在 render 结束后立即执行
  • useEffect 在大部分场景下比 Class 方式性能更好

适用场景

  • 数据获取(API 调用)
  • 订阅/取消订阅
  • 手动修改 DOM(不影响布局的情况)
  • 日志记录

基本用法

jsx
import React, { useEffect } from 'react';

function Example() {
  useEffect(() => {
    // 副作用逻辑
    console.log('组件挂载或更新');
    
    return () => {
      // 清理函数
      console.log('组件卸载');
    };
  }, [/* 依赖数组 */]);
  
  return <div>Example</div>;
}

useLayoutEffect

基本特性

useLayoutEffect 与 useEffect 的签名完全相同,但执行时机不同。

执行时机:

  • 在 DOM 更新完成后立即执行
  • 在浏览器进行任何绘制之前运行完成
  • 同步执行,阻塞浏览器绘制

适用场景

  • 需要测量 DOM 节点布局
  • 修改 DOM 并改变页面样式
  • 需要避免视觉闪烁(闪屏)的情况
  • 同步执行必要的 DOM 操作

为什么要阻塞绘制?

当 useEffect 中的操作需要处理 DOM 并且会改变页面样式时,如果不用 useLayoutEffect,可能会出现闪屏问题。这是因为:

  1. 浏览器先绘制了初始状态
  2. useEffect 异步执行,修改了 DOM
  3. 用户看到内容闪烁或跳动

useLayoutEffect 在绘制前同步执行,确保用户看到的是最终状态。

实际对比示例

使用 useEffect 的问题

jsx
import React, { useEffect, useRef } from "react";
import TweenMax from "gsap/TweenMax";

const Animate = () => {
  const REl = useRef(null);
  
  useEffect(() => {
    // 将方块移动到 600px 位置
    TweenMax.to(REl.current, 0, { x: 600 });
  }, []);
  
  return (
    <div className='animate'>
      <div ref={REl} className="square">
        square
      </div>
    </div>
  );
};

现象: 可以清楚看到有一个一闪而过的方块(闪屏)

原因:

  1. 组件渲染,方块在初始位置(0px)
  2. 浏览器绘制,用户看到方块
  3. useEffect 异步执行,方块移动到 600px
  4. 用户看到方块从 0px 跳到 600px

使用 useLayoutEffect 解决

jsx
import React, { useLayoutEffect, useRef } from "react";
import TweenMax from "gsap/TweenMax";

const Animate = () => {
  const REl = useRef(null);
  
  useLayoutEffect(() => {
    // 将方块移动到 600px 位置
    TweenMax.to(REl.current, 0, { x: 600 });
  }, []);
  
  return (
    <div className='animate'>
      <div ref={REl} className="square">
        square
      </div>
    </div>
  );
};

现象: 每次刷新,页面基本没变化,看不到闪屏

原因:

  1. 组件渲染
  2. useLayoutEffect 同步执行,方块直接设置在 600px 位置
  3. 浏览器绘制,用户直接看到最终状态

执行时机对比图

text
组件渲染流程:

1. Render 阶段
   ↓
2. DOM 更新
   ↓
3. useLayoutEffect 执行(同步,阻塞绘制)
   ↓
4. 浏览器绘制(Paint)
   ↓
5. useEffect 执行(异步,不阻塞)

选择指南

场景推荐 Hook原因
数据获取useEffect不阻塞渲染,性能更好
事件订阅useEffect不需要同步执行
修改 DOM 样式useLayoutEffect避免闪屏
测量 DOM 尺寸useLayoutEffect需要在绘制前获取准确值
动画初始位置useLayoutEffect避免初始位置闪烁
日志记录useEffect不需要阻塞

注意事项

1. 服务端渲染(SSR)

useLayoutEffect 会在服务端渲染时触发警告,因为服务端没有 DOM。如果组件需要在服务端渲染,可以使用以下方式:

jsx
import React, { useEffect, useLayoutEffect } from 'react';

const useIsomorphicLayoutEffect = typeof window !== 'undefined' 
  ? useLayoutEffect 
  : useEffect;

2. 性能影响

由于 useLayoutEffect 阻塞浏览器绘制,过度使用会影响应用性能。仅在必要时使用。

3. 默认值

如果不确定用哪个,始终优先使用 useEffect。只有当出现视觉问题时,才考虑切换到 useLayoutEffect。

总结

  • useEffect:异步执行,不阻塞绘制,适用于大多数场景
  • useLayoutEffect:同步执行,阻塞绘制,适用于需要避免闪屏的 DOM 操作

记住这个简单的原则:先尝试 useEffect,如果遇到闪屏问题,再换成 useLayoutEffect。


原文作者: LastStranger
来源: 简书
阅读数: 45,726

分享: