React18 函数组件执行顺序、严格模式下重复执行问题

在 React 中,函数组件的执行顺序和"为什么会执行两次"是很多人升级到 React18 后最容易困惑的问题之一。


一、React18 函数组件执行顺序

先看一个典型例子:

复制代码
import { useEffect, useLayoutEffect, useState } from 'react';

export default function App() {
  console.log('1. 组件函数执行');

  const [count, setCount] = useState(() => {
    console.log('2. useState 初始化');
    return 0;
  });

  useLayoutEffect(() => {
    console.log('3. useLayoutEffect');
  }, []);

  useEffect(() => {
    console.log('4. useEffect');
  }, []);

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

正常模式下首次渲染顺序

执行结果:

复制代码
1. 组件函数执行
2. useState 初始化
3. useLayoutEffect
4. useEffect

二、React18 渲染阶段与提交阶段

React18 可以分成:

1. Render 阶段(渲染阶段)

这一阶段:

  • 执行函数组件

  • 执行 hooks

  • 生成 Virtual DOM

这里不能做副作用:

❌ 不要:

复制代码
fetch()
localStorage.setItem()
订阅事件

因为 React 可能:

  • 中断 render

  • 重试 render

  • 丢弃 render

所以 render 必须纯净。


2. Commit 阶段(提交阶段)

React 真正更新 DOM。

然后执行:

复制代码
useLayoutEffect
↓
浏览器绘制
↓
useEffect

三、useLayoutEffect 与 useEffect 顺序

useLayoutEffect

同步执行。

DOM 更新后立即执行,浏览器还没绘制。

适合:

  • DOM 测量

  • scroll

  • 动画定位


useEffect

异步执行。

浏览器绘制完成后执行。

适合:

  • 请求

  • 订阅

  • 日志


四、为什么 React18 会执行两次?

这是最核心的问题。


React18 StrictMode 开启后:

开发环境下:

复制代码
<React.StrictMode>
  <App />
</React.StrictMode>

React 会故意:

组件 mount → unmount → 再 mount

目的:

检查:

  • 副作用是否安全

  • cleanup 是否完整

  • 是否有不纯 render


五、React18 严格模式下真实执行顺序

示例:

复制代码
function App() {
  console.log('render');

  useEffect(() => {
    console.log('effect');

    return () => {
      console.log('cleanup');
    };
  }, []);

  return <div>hello</div>;
}

开发环境 StrictMode:

输出:

复制代码
render
render

effect
cleanup
effect

六、为什么 render 执行两次?

React18 在开发环境会:

Double Invoke(双调用)

包括:

  • 函数组件

  • useState initializer

  • useMemo

  • useReducer initializer

例如:

复制代码
const [state] = useState(() => {
  console.log('init');
  return 0;
});

会输出:

复制代码
init
init

七、哪些会重复执行?

会重复:

1. 函数组件

复制代码
function App() {}

2. useEffect

复制代码
useEffect(() => {})

会:

复制代码
执行
cleanup
再执行

3. useLayoutEffect

同样也会。


4. useState 初始化函数

复制代码
useState(() => {})

5. useMemo

复制代码
useMemo(() => {})

八、生产环境会不会?

不会。

严格模式双执行:

✅ 仅开发环境

❌ 生产环境不会

所以:

复制代码
npm run build

后不会重复。


九、为什么 React 要这样设计?

React18 引入:

Concurrent Rendering(并发渲染)

未来 React 可能:

  • 暂停 render

  • 恢复 render

  • 重试 render

  • 丢弃 render

所以 React 需要确保:

render 必须是"纯函数"

即:

相同输入 → 相同输出

不能有副作用。


十、最容易踩坑的地方


1. useEffect 发请求两次

例如:

复制代码
useEffect(() => {
  fetch('/api/user');
}, []);

开发环境会请求两次。


正确理解:

不是 bug。

是 StrictMode 检查副作用。


十一、如何避免请求两次?


方法1:接受它(推荐)

这是 React 官方推荐。

因为生产环境不会重复。


方法2:使用 ref 防重复

复制代码
const hasRequest = useRef(false);

useEffect(() => {
  if (hasRequest.current) return;

  hasRequest.current = true;

  fetchData();
}, []);

方法3:封装请求层缓存

例如:

  • SWR

  • React Query

它们天然去重。

例如:

TanStack Query 官方文档

SWR 官方文档


十二、React18 完整生命周期顺序(函数组件)

首次挂载:

复制代码
render
↓
生成 Virtual DOM
↓
DOM commit
↓
useLayoutEffect
↓
浏览器 paint
↓
useEffect

更新时:

复制代码
render
↓
diff
↓
commit
↓
cleanup(old effect)
↓
new effect

卸载时:

复制代码
cleanup

十三、StrictMode 下的核心记忆

开发环境:

复制代码
mount
↓
effect
↓
cleanup
↓
重新 mount
↓
effect

React 是故意模拟卸载重建。


十四、面试高频总结

React18 为什么组件执行两次?

答案:

React18 在开发环境下的 StrictMode 中,会故意对组件进行 mount → unmount → remount,用于检测副作用与不安全代码,为 Concurrent Rendering 做准备。


useEffect 为什么执行两次?

因为 StrictMode 会重新挂载组件,所以 effect 会重新执行一次。


生产环境会吗?

不会,仅开发环境。


十五、最佳实践


render 阶段不要做:

复制代码
fetch()
setTimeout()
addEventListener()
localStorage.setItem()

effect 中一定要 cleanup:

复制代码
useEffect(() => {
  const timer = setInterval(() => {}, 1000);

  return () => clearInterval(timer);
}, []);

推荐:

  • render 保持纯净

  • effect 做副作用

  • cleanup 做释放

  • 接受 StrictMode 双执行


十六、推荐官方资料

React 官方 StrictMode 文档

React 官方 useEffect 文档

React 官方渲染机制说明

相关推荐
之歆1 小时前
DAY_20JavaScript 条件语句与循环结构深度学习(一)
前端·javascript
lihaozecq1 小时前
从零实现一个 ReAct Agent Loop - 可中断、可流式、多模型支持
前端·agent·ai编程
冴羽yayujs1 小时前
GitHub 前端热榜项目 - 日榜(2026-05-10)
前端·github
CAE虚拟与现实1 小时前
前后端调试常用工具大全
前端·后端·vue·react·angular
iuu_star1 小时前
跑通最简单的Vue3+Python前后端分离项目
前端·vue.js·python
AZaLEan__1 小时前
CSS3:从 2D 变换到 3D 翻转
前端·3d·css3
剑神一笑1 小时前
Linux du 命令深度解析:从磁盘占用统计到目录空间分析
linux·运维·前端
weixin_446260851 小时前
AI驱动的前沿前端技术栈深度解析:从模型能力到UI封装的完整生命周期
前端·人工智能·ui
程序猿编码1 小时前
Linux 高负载场景下 Web 服务访问日志极速定位工具实现解析(C/C++代码实现)
linux·服务器·c语言·前端·c++