React:Render vs LayoutEffect vs Effect

React Hooks Execution Timing: Render vs LayoutEffect vs Effect

在 React 函数组件中,逻辑代码的执行时机主要分为三个阶段。理解它们的区别对于优化性能和避免 Bug 至关重要。

1. 核心对比表格 (Comparison Table)

方式 执行时机 所处阶段 阻塞 UI 绘制? 核心应用场景
函数体直接执行(Direct Execution) 组件函数被调用时,Virtual DOM 构建期间 Render Phase 是 (计算耗时会导致卡顿) 派生状态计算 (Derived State)、简单过滤、常量定义
useLayoutEffect DOM 更新完成,但浏览器把内容画到屏幕之前 Commit Phase (Pre-paint) (同步执行) DOM 测量、读取布局、为了防止画面闪烁的样式修正
useEffect DOM 更新完成,且浏览器已经把内容画到屏幕之后 Commit Phase (Post-paint) (异步执行) 副作用处理 (API 请求、埋点、订阅、定时器)

2. 执行流程图 (Execution Flow)

graph TD
    A[组件 State/Props 改变] --> B(React 调用组件函数)
    B -- 1. 函数体直接执行 --> C[构建 Virtual DOM]
    C --> D[React 更新真实 DOM]
    D -- 2. useLayoutEffect 执行 --> E{浏览器绘制 Paint}
    E --> F[用户看到新界面]
    F -- 3. useEffect 执行 --> G[后续逻辑]
  
    style B fill:#f9f,stroke:#333
    style D fill:#bbf,stroke:#333
    style E fill:#ff9,stroke:#333

3. 详细场景说明 (Detailed Scenarios)

3.1 直接在函数体中执行 (Render Phase)

这是 React 构建 Virtual DOM 的过程。每次组件更新都会重新运行这部分代码。

  • ✅ 适用:纯计算逻辑(如 const double = count * 2)。
  • ❌ 禁忌:不要在这里写副作用(如 HTTP 请求、setState),因为在 React 的并发模式(Concurrent Mode)下,渲染阶段可能会被暂停、废弃或重复执行多次。
1
2
3
4
5
6
7
8
9
10
function MyComponent({ users, filterText }) {
// ✅ 正确:纯计算逻辑
// 只要 props 变化,这就立即重新计算,不会造成额外的渲染
const filteredUsers = users.filter(u => u.name.includes(filterText));

// ❌ 错误:不要在这里发起请求或直接修改 DOM
// fetch('/api/...');

return <div>{filteredUsers.length}</div>;
}

3.2 useLayoutEffect (Commit Phase - Sync)

它的执行时机和类组件的 componentDidMount / componentDidUpdate 几乎一致。

  • ✅ 适用:需要操作 DOM 且不希望用户看到中间状态的场景(如 tooltip 定位、动画起始位置计算)。
  • ⚠️ 注意:它会阻塞浏览器绘制,使用过度会导致页面卡顿。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { useLayoutEffect, useRef } from 'react';

function Tooltip() {
const ref = useRef(null);

useLayoutEffect(() => {
// ✅ 场景:我们需要知道元素的真实高度,来决定它向上还是向下弹出
// 此时 DOM 已经在内存中更新了,但还没画到屏幕上
const { height } = ref.current.getBoundingClientRect();

if (height > 100) {
// 立即调整样式
// 因为浏览器还没绘制,用户不会看到元素"跳动"的过程
ref.current.style.top = '10px';
}
}, []);

return <div ref={ref}>Tooltip Content</div>;
}

3.3 useEffect (Commit Phase - Async)

这是 95% 的业务场景应该使用的 Hook。 它不会阻塞浏览器更新屏幕,能保证页面响应最快。

  • ✅ 适用:数据获取、日志上报、建立 WebSocket、非视觉相关的 DOM 操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { useEffect } from 'react';

function UserProfile({ userId }) {
useEffect(() => {
// ✅ 场景:API 请求
// 这些都不需要阻塞 UI 显示,可以让用户先看到 "Loading..."
// 等数据回来了再更新界面

let isMounted = true;
fetch(`/api/user/${userId}`).then(data => {
if (isMounted) setUser(data);
});

return () => { isMounted = false; }; // 清理函数
}, [userId]);

return <div>User Profile</div>;
}