什么是 StrictMode?
StrictMode 是 React 提供的开发者工具,它不渲染任何可见的 UI,而是通过为后代组件提供额外的检查来帮助开发者编写高质量代码。它的核心目标是:
- 识别不安全的生命周期:为并发模式扫清障碍。
- 警告过时的 API 使用:确保代码能平滑升级到新版本。
- 检测意外的副作用:通过故意重复调用来暴露 Bug。
StrictMode 的"反直觉"行为
在开发模式下(仅限开发环境,生产环境不生效),StrictMode 会强制执行以下操作:
- 重复渲染(Double Render) :组件函数体会被执行两次。
- 重复 Effect(Double Effect) :执行
Mount→Unmount→Mount的完整周期。
核心解密:为什么要"卸载再挂载"?
很多开发者认为重复请求是 StrictMode 的"副作用",但实际上这是 React 为了未来特性所做的强制演习。
1. 为 React 19+ 的 Activity (Offscreen) API 做准备
React 正在引入一种能力(即将在 React 19 及后续版本完善的 <Activity>),允许组件在切换页面时不被销毁,而是"休眠"在后台。
- 场景:用户从 Tab A 切到 Tab B,再切回 Tab A。
- 目标:Tab A 的状态(滚动位置、表单内容)瞬间恢复,无需重新请求。
- StrictMode 的良苦用心 :现在的
Mount -> Unmount -> Mount就是在模拟"休眠 -> 唤醒"的过程。如果你的组件在Unmount时清理不彻底(如未取消订阅),这种 Bug 在 StrictMode 下会立即暴露,防止未来上线 Activity 功能时页面崩溃。
2. 确保并发渲染(Concurrent Rendering)的稳定性
React 19 全面拥抱并发。渲染过程可能会被中断、暂停或废弃。如果不保证渲染函数的"纯度"(Idempotency),多次计算可能会导致 UI 状态不一致。StrictMode 的重复执行正是为了确保无论渲染多少次,结果都是确定的。
典型的"重复请求"现象
TypeScript
<StrictMode>
<RouterProvider router={router} />
</StrictMode>
在 StrictMode 下,常见的请求流程是:
- 组件首次挂载 → 执行
useEffect→ 发起第 1 次网络请求 - React 立即卸载组件 → 执行
cleanup - React 重新挂载组件 → 再次执行
useEffect→ 发起第 2 次网络请求(内容相同)
最佳实践与解决方案
在 React 19 时代,我们强烈建议保留 StrictMode。与其关掉它,不如优化你的数据获取策略。
方案 1:使用数据请求库(React 19 推荐)
这是现代 React 开发的"标准答案"。使用 TanStack Query (React Query) 、SWR 或 React 19 的 Server Components (RSC) 。
TypeScript
// 使用 React Query,库内部会自动处理去重和缓存
// StrictMode 下的多次挂载不会导致重复的网络请求
const { data } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
});
- 优势 :直接物理隔离了
useEffect的副作用问题,代码更声明式,完美适配 StrictMode。
方案 2:使用 AbortController(手动挡推荐)
如果你必须在 useEffect 中请求,请使用 AbortController 进行竞态处理和取消。这不仅解决了 StrictMode 的视觉干扰,更防止了网络竞态条件(Race Condition)。
TypeScript
useEffect(() => {
const abortController = new AbortController();
const fetchData = async () => {
try {
const res = await apiClient.request({
method: 'POST',
url: '/get/data',
signal: abortController.signal // 绑定信号
});
// 处理成功逻辑
} catch (error) {
if (error.name !== 'AbortError') {
// 真正的错误处理
console.error(error);
}
}
};
fetchData();
// Cleanup: 第一次卸载时取消请求 A,保留请求 B
return () => {
abortController.abort();
};
}, []);
避坑:慎用 useRef 标记
一种常见的"歪门邪道"是使用 useRef 拦截请求:
TypeScript
// ❌ 不推荐:可能导致依赖更新失效
const hasRequested = useRef(false);
useEffect(() => {
if (hasRequested.current) return;
hasRequested.current = true;
fetchData();
}, [userId]); // 如果 userId 变化,ref 依然是 true,会导致请求被拦截
这种方法虽然能在这个场景下生效,但如果依赖项数组(Dependency Array)不为空,很容易导致后续合法的请求被错误拦截,增加维护成本。
总结
在 React 19 中,StrictMode 不仅仅是一个调试工具,它是代码质量的体检员。
- 不要移除它:它帮助你确保代码兼容未来的"后台保活"特性和并发渲染。
- 正确处理副作用:重复请求不是 Bug,而是提示你需要清理副作用(Cleanup)或使用更现代的数据流方案(如 React Query 或 RSC)。
- 拥抱标准 :适应
StrictMode的检查,意味着你的代码已经做好了迎接 React 未来新特性的准备。