← 返回题目列表

React StrictMode 为什么会让某些逻辑执行两次?

中等 第 20 / 27 题 更新于 2026/07/29
ReactStrictMode副作用开发模式

简化版

React StrictMode 在开发模式下会故意重复调用某些渲染和副作用流程,用来暴露不纯渲染、未清理订阅、重复请求等问题。它不影响生产环境,但提醒你副作用必须可清理、渲染必须保持纯净。

详细版

React 18 开发模式中,StrictMode 可能让组件经历挂载、清理、再挂载的检查流程。

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

你可能看到 subscribe -> cleanup -> subscribe。这不是 React 生产环境 bug,而是开发期检查。正确做法不是关掉 StrictMode 掩盖问题,而是保证 effect 有清理逻辑、请求有取消或幂等处理、渲染阶段不做副作用。

完整版教学

一、StrictMode 是开发期体检器

StrictMode 不会渲染任何真实 DOM。 它包住组件树后,会启用一组开发模式检查,帮助你提前发现未来并发渲染和重复挂载下的隐患。 最容易被注意到的就是某些逻辑看起来执行了两次。

<React.StrictMode>
  <App />
</React.StrictMode>

如果代码只在“永远只执行一次”的假设下正确,遇到重试、恢复、并发中断就可能出问题。 StrictMode 用更严格的开发体验逼你写出可重复、可清理的代码。

二、渲染阶段必须是纯计算

React 组件函数本身属于渲染阶段。 渲染阶段应该只根据 props 和 state 返回 UI,不应该发请求、改全局变量、订阅事件或写 DOM。 如果渲染函数被调用两次就出错,说明它不纯。

function BadComponent() {
  analytics.track('render') // 不推荐:渲染阶段副作用
  return <div>hello</div>
}
位置是否适合副作用原因
组件函数体不适合可能被重复调用或丢弃
useMemo 计算函数不适合副作用仍应保持纯计算
useEffect适合异步副作用可清理
事件处理函数适合用户触发的副作用来源明确

比如组件函数里给全局计数器加 1,双调用后数字会变成 2。 这不是 React 错,而是代码把副作用放错位置。

三、effect 要能挂载、清理、再挂载

开发模式下 StrictMode 会模拟组件重新挂载,检查 effect 清理是否完整。 如果你订阅事件但不移除,重复挂载会造成多个监听器。 真实项目里,路由切换或条件渲染也会触发同样问题。

useEffect(() => {
  const handler = () => console.log('resize')
  window.addEventListener('resize', handler)
  return () => window.removeEventListener('resize', handler)
}, [])
开发检查:
mount -> effect
unmount -> cleanup
mount -> effect

如果少了 cleanup,窗口 resize 一次可能打印 2 次、3 次,随着挂载次数增加越来越多。 StrictMode 让这个问题更早暴露。

四、重复请求要靠取消、缓存或幂等处理

很多人第一次遇到 StrictMode 双执行,是发现接口请求发了两次。 真实解决思路不是简单删除 StrictMode,而是让请求逻辑可控。 例如使用 AbortController 取消旧请求,或交给 React Query/SWR 做缓存和去重。

useEffect(() => {
  const controller = new AbortController()
  fetch('/api/user', { signal: controller.signal })
  return () => controller.abort()
}, [])

假设一次请求 120ms,开发模式两次请求可能让你误以为性能翻倍下降。 但生产环境不会因为 StrictMode 额外双发。 真正要关注的是请求是否能取消、是否能抵抗重复提交、接口是否幂等。

五、它和 React 18 并发能力有关

并发渲染里,React 可能开始渲染一棵树,然后因为更高优先级更新中断它。 也可能为了恢复 UI、重试渲染而重复执行某些流程。 如果代码依赖“渲染只发生一次”,未来就不安全。

并发思路:
开始渲染 A
高优先级输入来了
暂停或丢弃 A
先处理输入
之后重新渲染 A

StrictMode 让开发者在普通本地开发中也能感受到这些约束。 它不是为了折磨人,虽然有时确实像在轻轻拍你脑门。

六、生产环境不会做这类开发检查

StrictMode 的重复调用主要发生在开发模式。 生产构建不会因为它额外执行这些检查。 但不要因此忽略问题,因为开发期暴露的副作用不纯,在真实卸载重挂、网络重试、并发中断中仍可能造成 bug。

记忆钩子:StrictMode 不是让生产跑两遍,而是在开发时问你“代码重来一次还稳不稳”。

面试回答时要避免只说“开发环境会执行两次”。 更重要的是说清楚为什么:检查渲染纯度和副作用清理能力。

七、常见误区与追问

  • 误区:StrictMode 导致生产环境接口也发两次。 这类重复检查发生在开发模式,生产构建不会因此双执行。
  • 误区:解决方式就是删掉 StrictMode。 更应该修复不纯渲染、缺失清理和重复副作用。
  • 误区:空依赖 useEffect 永远只执行一次。 在开发 StrictMode 检查中,可能经历挂载、清理、再挂载。
  • 追问:渲染阶段为什么不能发请求? 渲染可能被重复、暂停或丢弃,副作用应放到 effect 或事件中。
  • 追问:重复请求怎么处理? 使用取消、缓存去重、请求幂等或成熟数据请求库。
  • 追问:StrictMode 会影响页面 DOM 吗? 它本身不额外渲染 DOM 节点,只启用开发检查。

八、加强记忆

StrictMode 这题抓“开发检查”和“副作用纪律”。组件渲染要纯,effect 要清理,请求要能取消或去重。看到执行两次先别慌,这是 React 在开发模式提前帮你抓隐藏问题。