文章目录

React Strict Mode严格模式下为什么组件渲染两次

发布于 2026-05-03 10:24:29 · 浏览 15 次 · 评论 0 条

React Strict Mode严格模式下为什么组件渲染两次

React 18 在开发环境下开启了 Strict Mode(严格模式),这会导致组件、状态更新函数、以及 useEffect 回调执行两次。这种行为并非 Bug,而是 React 故意设计的特性,用于帮助开发者发现代码中潜在的副作用问题。

以下步骤将带你从现象验证到原理剖析,最后编写出符合严格模式要求的代码。


1. 验证“双重渲染”现象

创建一个简单的函数组件,在组件内部加入 console.log 以观察渲染次数。

打开你的 React 项目,新建或修改 App.js 文件,输入以下代码:

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

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

  console.log('组件渲染中... 当前 count:', count);

  useEffect(() => {
    console.log('useEffect 执行 (模拟组件挂载)');

    // 模拟订阅事件
    const handleResize = () => {
      console.log('窗口大小改变');
    };
    window.addEventListener('resize', handleResize);

    // 模拟清理函数
    return () => {
      console.log('useEffect 清理函数执行 (模拟组件卸载)');
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  return (
    <div style={{ padding: '20px' }}>
      <h1>计数器: {count}</h1>
      <button onClick={() => setCount(count + 1)}>点击增加</button>
    </div>
  );
}

export default App;

保存文件并打开浏览器控制台(按 F12 或右键“检查”)。你会发现控制台输出了两次“组件渲染中...”和“useEffect 执行”,紧接着输出了两次“useEffect 清理函数执行”。这表明 React 模拟了组件的“挂载-卸载-重新挂载”过程。


2. 理解“副作用”与双重渲染的关系

React 严格模式下的双重渲染,核心目的是检测暴露不安全的副作用。

在 React 中,“副作用”是指除了返回 JSX 之外的操作,例如:

  1. 修改全局变量。
  2. 订阅外部数据源(如 WebSocket、事件监听器)。
  3. 手动操作 DOM。

如果在 useEffect 中写了订阅逻辑但忘记了写清理逻辑,或者直接在组件渲染期间执行了副作用,双重渲染会导致以下问题:

  • 第一次渲染订阅了事件 A。
  • 模拟卸载时,如果没有清理,事件 A 依然存在。
  • 第二次渲染又订阅了事件 A。
  • 结果:同一个事件被触发了两次,导致内存泄漏或逻辑错误。

3. 直观查看渲染流程

为了更清晰地理解 React 18 在开发环境下的执行顺序,我们可以通过以下流程图来还原这一过程。该流程仅在开发模式且开启 Strict Mode 时发生,生产环境不会执行“卸载与重新挂载”的步骤。

graph TD A[组件挂载 Mount] --> B[第一次渲染
执行 Effect] B --> C[React 强制模拟卸载
执行 Cleanup 清理函数] C --> D[React 强制模拟重新挂载
再次渲染 执行 Effect] D --> E[组件稳定]

注意:上图中的“模拟卸载”与“模拟重新挂载”正是控制台打印两次日志的原因。这确保了如果你的组件没有写清理函数,在开发阶段就能立刻暴露出问题(例如事件监听器重复绑定)。


4. 编写符合严格模式的健壮代码

为了适应严格模式,必须确保 useEffect 的清理函数逻辑正确。即:有添加必须有移除有订阅必须有取消订阅

修改之前的代码,确保清理逻辑完整(参考步骤 1 中的代码示例)。当你编写具有副作用的 Effect 时,请遵循以下模式:

  1. 定义副作用逻辑(如添加事件监听)。
  2. 返回一个清理函数,并在其中移除副作用。

例如,如果在 useEffect调用document.title,这通常不需要清理。但如果使用了 timer(定时器),则必须编写清理逻辑:

useEffect(() => {
  const timer = setInterval(() => {
    console.log('定时器运行中');
  }, 1000);

  // 必须返回清理函数
  return () => clearInterval(timer);
}, []);

测试上述代码,你会发现即使组件渲染两次,前一个定时器也会在后一个定时器启动前被清除,控制台不会出现多个定时器同时运行的混乱情况。


5. 区分开发环境与生产环境

双重渲染仅存在于开发环境,目的是辅助调试。一旦项目打包上线,这种行为会自动消失,以优化性能。

下表总结了不同环境下的行为差异:

环境 Strict Mode 状态 组件渲染次数 Effect 执行次数 卸载模拟
开发环境 开启 2 次 2 次 执行
生产环境 关闭 1 次 1 次 不执行

检查你的 package.json确认构建命令使用的是 react-scripts buildvite build,这会自动生成生产环境构建版本。在生产构建中,React 会移除所有开发辅助警告和额外的渲染检查。


6. 关闭 Strict Mode(不推荐)

如果项目历史代码遗留问题过多,修复成本过高,你可以临时关闭 Strict Mode 以消除双重渲染带来的干扰。

打开 src/index.js(或 src/main.jsx),找到 <React.StrictMode> 标签。

删除该标签,或者将其替换为普通的 <div><Fragment>

修改前

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

修改后

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <App />
);

注意:关闭 Strict Mode 虽然能停止双重渲染,但同时也失去了 React 提供的自动检查不安全生命周期、废弃 API 使用等安全防护能力。建议优先修复副作用代码,而非关闭该模式。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文