You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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会执行两次;而生产环境打包后这个行为会自动消失,不会出现重复执行的情况。

解决办法

  1. 临时关闭Strict Mode(不推荐长期使用)
    找到项目入口文件,去掉<StrictMode>标签包裹即可:

    // 原代码
    ReactDOM.createRoot(document.getElementById('root')).render(
      <StrictMode>
        <App />
      </StrictMode>
    );
    
    // 修改后
    ReactDOM.createRoot(document.getElementById('root')).render(
      <App />
    );
    

    但不建议一直关闭,因为Strict Mode能帮你提前发现很多隐藏问题。

  2. 优化副作用逻辑,避免重复请求的影响
    可以给组件加一个状态标记数据是否已加载,防止重复请求:

    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 06:12:01