记排查React.StrictMode多次调用的小坑

1,903 阅读2分钟

这是我参与更文挑战的第2天,活动详情查看:更文挑战

今天在知乎上看到这样一个题:www.zhihu.com/question/46…

Screen Shot 2021-06-19 at 4.44.29 PM.png

组件渲染一遍的时候,变量却被加了两次,我仔细阅读了代码也是百思不得其解,于是顺着截图的链接:bufop.csb.app/ 去调了调,终于找到了原因。

先说结论:原因是 React.StrictMode 多次调用问题,导致这个函数组件被调用了两次

1. 排查过程

首先删除一下无关紧要的代码,变成:

let counter = 0

export default function App() {
  useEffect(() => {
    console.log(77771, counter)
  })
  
  console.log(77772, counter)
  counter += 2
  
  return null
}

发现输出还是77771中的 counter 还是4,怀疑是 useEffect 的坑,于是改成 setTimeout:

let counter = 0

export default function App() {
  console.log(77772, counter)
  counter += 2
  
  setTimeout(() => console.log(77773, counter), 1000)

  return null
}

发现77773还是4,但这次打印了2次:

origin_img_v2_e2648a2b-ca20-4e7a-a447-35d50536937g.PNG

这么简单的代码没道理出现这种问题啊!于是去查其他文件,看到 index.js 里用对 App 组件做了如下包裹:

ReactDOM.render(
  <StrictMode>
    <App />
  </StrictMode>,
  rootElement
);

或许是 StrictMode 的原因?删掉它试了下,果然正常了!

2. 定位原因

找到问题在哪,定位原因就容易多了,百度搜“StrictMode多次渲染”,很快发现大家早就知道了这个问题,浏览了几篇,大概是说在 StrictMode 下,组件的一些方法会被调用多次,了解原因后,于是去翻 官方文档 加以确认:

严格模式不能自动检测到你的副作用,但它可以帮助你发现它们,使它们更具确定性。通过故意重复调用以下函数来实现的该操作:

  • class 组件的 constructor,render 以及 shouldComponentUpdate 方法
  • class 组件的生命周期方法 getDerivedStateFromProps
  • 函数组件体
  • 状态更新函数 (即 setState 的第一个参数)
  • 函数组件通过使用 useState,useMemo 或者 useReducer

确认了 函数组件在 StrictMode 下会被重复调用,至此还剩下一个小问题:既然组件函数被多调用了一次,那么 console.log 应该成对出现,为啥同步的 console.log 会少一个?继续看文档:

注意: 从 React 17 开始,React 会自动修改 console 的方法,例如 console.log(),以在对生命周期函数的第二次调用中静默日志。然而,在某些可以使用替代解决方案的情况下,这可能会导致一些不期望的行为的发生。

原来 console 被 React 修改了,好吧。

3. 小结

在 React.StrictMode 下,React 通过重复调用组件的一些钩子,从而使副作用更容易暴露出来;同时,它仅在开发环境中运行,不会影响生产构建。

参考:严格模式-React