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 之外的操作,例如:
- 修改全局变量。
- 订阅外部数据源(如 WebSocket、事件监听器)。
- 手动操作 DOM。
如果在 useEffect 中写了订阅逻辑但忘记了写清理逻辑,或者直接在组件渲染期间执行了副作用,双重渲染会导致以下问题:
- 第一次渲染订阅了事件 A。
- 模拟卸载时,如果没有清理,事件 A 依然存在。
- 第二次渲染又订阅了事件 A。
- 结果:同一个事件被触发了两次,导致内存泄漏或逻辑错误。
3. 直观查看渲染流程
为了更清晰地理解 React 18 在开发环境下的执行顺序,我们可以通过以下流程图来还原这一过程。该流程仅在开发模式且开启 Strict Mode 时发生,生产环境不会执行“卸载与重新挂载”的步骤。
执行 Effect] B --> C[React 强制模拟卸载
执行 Cleanup 清理函数] C --> D[React 强制模拟重新挂载
再次渲染 执行 Effect] D --> E[组件稳定]
注意:上图中的“模拟卸载”与“模拟重新挂载”正是控制台打印两次日志的原因。这确保了如果你的组件没有写清理函数,在开发阶段就能立刻暴露出问题(例如事件监听器重复绑定)。
4. 编写符合严格模式的健壮代码
为了适应严格模式,必须确保 useEffect 的清理函数逻辑正确。即:有添加必须有移除,有订阅必须有取消订阅。
修改之前的代码,确保清理逻辑完整(参考步骤 1 中的代码示例)。当你编写具有副作用的 Effect 时,请遵循以下模式:
- 定义副作用逻辑(如添加事件监听)。
- 返回一个清理函数,并在其中移除副作用。
例如,如果在 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 build 或 vite 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 使用等安全防护能力。建议优先修复副作用代码,而非关闭该模式。

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