在 React 中,当你看到某些代码执行了多次(例如,console.log
输出了两次),这通常是由几个常见原因导致的:
-
严格模式(Strict Mode) :
在开发模式下,React 的严格模式会故意使组件重新渲染两次,以便更容易发现潜在的副作用和其他问题。你可以检查你的
index.js
或者main.js
文件,看看是否有<React.StrictMode>
包裹了你的应用。 -
双重渲染 :
React 可能会因为某些状态变化或者父组件的重新渲染导致子组件重新渲染多次。
-
热模块替换(Hot Module Replacement, HMR) :
在开发过程中,HMR 可能会导致组件重新加载,尤其是在使用一些开发工具和环境时。
解决方法
为了检查和解决这个问题,你可以尝试以下步骤:
1. 检查是否启用了严格模式:
在 index.js
或 main.js
中,如果你看到如下代码,可以注释掉 <React.StrictMode>
并观察行为变化:
js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
// <React.StrictMode>
<App />,
// </React.StrictMode>,
document.getElementById('root')
);
2. 添加唯一的 key
属性:(切记,很重要!)
如果你在列表中渲染元素,确保每个元素都有唯一的 key
属性。这有助于 React 更好地管理列表项。
js
import React, { useState, useEffect } from 'react';
const App = () => {
const [listData, setListData] = useState([]);
useEffect(() => {
// 初始化加载数据
const initialData = Array.from({ length: 5 }, (_, i) => `Item ${i + 1}`);
setListData(initialData);
}, []);
return (
<div className="content">
{listData.map((item, index) => (
<div key={index} className="list-item">
{item}
</div>
))}
</div>
);
};
export default App;
3. 使用 useEffect
的依赖项:(最容易导致。。。。。。)
检查 useEffect
是否有明确的依赖项。比如:
jsx
useEffect(() => {
console.log('handleLoadMore');
handleLoadMore();
}, [依赖项数组]);
- 第一个参数是一个回调函数,其中包含副作用代码。
- 第二个参数是依赖项数组,控制副作用的执行时机。
当依赖项数组为空时,useEffect
仅在组件首次渲染后执行一次。之后即使组件重新渲染(比如因为状态或属性变化),该副作用也不会再次执行。
js
useEffect(() => {
// 依赖项变化时执行副作用
}, [dep1, dep2]);
当依赖项数组包含特定变量时,useEffect
会在组件首次渲染后执行,并在任何一个依赖项发生变化时重新执行。
js
useEffect(() => {
// 每次渲染后执行副作用
});
如果没有提供依赖项数组,useEffect
会在每次组件渲染后执行(记得设置一个空数组[])。这通常会导致性能问题,除非你的副作用非常轻量且必须每次渲染后都执行。
js
useEffect(() => {
const handle = setInterval(() => {
console.log('Interval running');
}, 1000);
return () => {
clearInterval(handle);
};
}, []);
有时候需要在组件卸载或副作用重新运行之前进行清理。例如,取消订阅或清除计时器。
4. 排除 HMR 的影响:
确保热模块替换没有干扰到你的调试。你可以在开发模式下尝试禁用 HMR。
(1)Create React App:
在你的 .env 文件中添加:
sh
FAST_REFRESH=false
如果没有 .env
文件,可以在启动命令前添加环境变量:
sh
FAST_REFRESH=false npm start
(2)Webpack:
Create React App 默认隐藏了 Webpack 配置,所以你需要使用 react-app-rewired
或 craco
来覆盖配置。以下是使用 craco
的示例:
sh
npm install @craco/craco
创建一个 craco.config.js
文件,禁用 HMR:
js
module.exports = {
webpack: {
configure: (webpackConfig, { env, paths }) => {
if (env === 'development') {
webpackConfig.devServer = {
...webpackConfig.devServer,
hot: false,
};
}
return webpackConfig;
},
},
};
修改 package.json
的启动脚本:
js
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject"
}
(3)自定义的 Webpack:
如果你有自定义的 Webpack 配置,可以直接在 webpack.config.js
中禁用 HMR:
js
module.exports = {
// ...其他配置
devServer: {
hot: false,
},
};
(4)Vite:
如果你使用 Vite 作为构建工具,可以通过修改 Vite 配置来禁用 HMR:
js
export default {
server: {
hmr: false,
},
};
(5)Next.js:
Next.js 使用其自己的 Webpack 配置,禁用 HMR 可能需要一些额外步骤:
自定义 next.config.js:
js
module.exports = {
webpackDevMiddleware: config => {
config.watchOptions = {
poll: 300,
aggregateTimeout: 300,
ignored: /node_modules/,
};
return config;
},
};
5. 调试工具:
使用 Facebook 的 React DevTools 调试工具,观察组件的渲染情况和状态变化,找到导致重复渲染的具体原因。