React.StrictMode 会导致多次渲染
故事
今天在研究一个第三方库的时候发现一个很简单的组件在 render
时被渲染了两次。精简后的组件代码大致如下:
// App.js
import React, { useState } from 'react';
function App() {
const [count] = useState(666);
console.log('render!!');
return (<div>{count}</div>);
}
export default App;
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
在控制台中会看到输出了两次 render!!
,使用的包版本如下:
{
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1"
}
仔细看了 React
的文档以及参考了很多 Google 上的问题,发现是因为使用了 React.StrictMode
render 导致的。
在开发环境中使用 StrictMode
的目的是为了:
- 识别不安全的生命周期
- 检测过时的 API 和废弃的方法
- 检测意外的副作用
React 在检测意外的副作用时可能重复调用某些生命周期方法、hooks 或者 render 方法,其包括:
- class 组件的
constructor
,render
以及shouldComponentUpdate
方法 - class 组件的生命周期方法
getDerivedStateFromProps
- 函数组件
- 状态更新函数 (即
setState
的第一个参数) - 函数组件中
useState
,useMemo
或者useReducer
中的函数
当 React 发现在重复调用这些方法时出现了内存泄漏、无限循环或者其他奇怪的表现时会在控制台输出错误,以供程序员快速定位错误。
在 Github 的某条 issue 中,Dan Abramov 也提到:
It is expected that setState updaters will run twice in strict mode in development. This helps ensure the code doesn’t rely on them running a single time (which wouldn’t be the case if an async render was aborted and alter restarted). If your setState updaters are pure functions (as they should be) then this shouldn’t affect the logic of your application.
简单来说就是我们在使用 hooks 或者在某些生命周期函数中不应该使用有副作用的代码。在开发模式的 StrictMode
下,React 会帮助我们发现这些不好的代码并给予提示。
StrictMode
的这个重复调用的特性只使用于开发模式,在生产模式下不会触发多次调用。
案例
github/ReactTraining/react-media/modules/Media.js
89 行使用了 useState
并在 useState
中调用 setUpMQLs
用以注册事件监听器。由于在 StrictMode
下 useState
中的函数会被执行 2 次(或多次),因此会导致某些事件监听器没有被 cancel 而一直存在,最终由于内存泄漏而被 React 检测到,控制台会输出 Can't perform a React state update on an unmounted component
(issue#139)。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于