React+Vite项目中useEffect挂载时触发两次的原因咨询
组件挂载时useEffect执行两次的原因及解决办法
我正在开发基于MERN栈、前端采用Vite+React的应用,使用useEffect Hook从数据库获取数据并在表格中展示。控制台无错误或警告,但页面刷新时,通过console.log发现useEffect被调用两次,导致重复查询数据。虽未影响功能,但想了解组件挂载时useEffect运行两次的具体原因。
相关代码如下:
import React, { useState, useEffect } from 'react'; import axios from 'axios'; const OperatingSites = () => { const [siteData, setSiteData] = useState([ { location: '', type: '', address: '' } ]); useEffect(() => { const fetchData = async () => { try { const response = await axios.get('http://localhost:5000/operatingSite'); const fetchData = response.data; setSiteData([...fetchData, { location: '', type: '', address: '' }]); } catch (error) { console.error('Error fetching data:', error); } }; console.log("UseEffect"); fetchData(); }, []); const handleInputChange = (index, event) => { const { name, value } = event.target; const newData = [...siteData]; newData[index][name] = value; setSiteData(newData); // Automatically add a new row if editing the last row and it's not empty if (index === newData.length - 1 && newData[index].location && newData[index].type && newData[index].address) { addEmptyRow(); } }; const addEmptyRow = () => { setSiteData([...siteData, { location: '', type: '', address: '' }]); }; const generateRows = () => { return siteData.map((site, index) => ( <tr key={index}> <td> <input type="text" className="w-full border-none focus:ring-transparent text-center" placeholder="Enter location" name="location" value={site.location} onChange={(e) => handleInputChange(index, e)} /> </td> <td> <select className="w-full border-none focus:ring-transparent text-center" name="type" value={site.type} onChange={(e) => handleInputChange(index, e)} > <option value="">Choose...</option> <option value="Primary">Primary</option> <option value="Secondary">Secondary</option> </select> </td> <td> <input type="text" className="w-full border-none focus:ring-transparent text-center" placeholder="Enter address" name="address" value={site.address} onChange={(e) => handleInputChange(index, e)} /> </td> </tr> )); }; const handleSubmit = async () => { const filteredData = siteData.filter(site => site.location && site.type && site.address); try { await axios.post('http://localhost:5000/operatingSite', filteredData); alert('Data submitted successfully'); } catch (error) { console.error('Error submitting data:', error); } }; return ( <div className="flex"> <div className="w-full pb-20 pt-8 pr-28 pl-28"> <div className="relative overflow-hidden "> <table className="w-full text-center border border-[#52B14A] "> <thead className="bg-[#00bdf6] h-10"> <tr> <th className="py-2">Location</th> <th className="py-2">Primary/Secondary</th> <th className="py-2">Address</th> </tr> </thead> <tbody className="bg-transparent "> {generateRows()} </tbody> </table> <button onClick={handleSubmit} className="mt-4 p-2 bg-blue-500 text-white"> Submit Changes </button> </div> </div> </div> ); }; export default OperatingSites;
控制台输出显示"UseEffect"打印两次。
原因分析
这是**React 18及以上版本的严格模式(Strict Mode)**在开发环境下的默认行为。
React 18的Strict Mode会在开发阶段强制触发两次组件挂载、更新相关的生命周期函数(包括useEffect),目的是帮你提前检测代码中存在的潜在问题:
- 未清理的定时器、订阅导致的内存泄漏
- 非幂等的副作用(多次执行会产生不一致结果)
- 意外的重复渲染逻辑
Vite创建的React项目默认会在入口文件(main.jsx/main.tsx)中启用Strict Mode,所以开发环境刷新页面时,useEffect会执行两次;而生产环境打包后这个行为会自动消失,不会出现重复执行的情况。
解决办法
临时关闭Strict Mode(不推荐长期使用)
找到项目入口文件,去掉<StrictMode>标签包裹即可:// 原代码 ReactDOM.createRoot(document.getElementById('root')).render( <StrictMode> <App /> </StrictMode> ); // 修改后 ReactDOM.createRoot(document.getElementById('root')).render( <App /> );但不建议一直关闭,因为Strict Mode能帮你提前发现很多隐藏问题。
优化副作用逻辑,避免重复请求的影响
可以给组件加一个状态标记数据是否已加载,防止重复请求:const [siteData, setSiteData] = useState([{ location: '', type: '', address: '' }]); const [isLoaded, setIsLoaded] = useState(false); // 新增状态 useEffect(() => { const fetchData = async () => { if (isLoaded) return; // 已加载则跳过 try { const response = await axios.get('http://localhost:5000/operatingSite'); const fetchData = response.data; setSiteData([...fetchData, { location: '', type: '', address: '' }]); setIsLoaded(true); // 标记为已加载 } catch (error) { console.error('Error fetching data:', error); } }; console.log("UseEffect"); fetchData(); }, [isLoaded]); // 依赖加入isLoaded或者使用axios的取消令牌(CancelToken)来取消第二次请求,避免不必要的网络开销。
内容的提问来源于stack exchange,提问作者Yasiru Basura
相关产品推荐
相关产品推荐

