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

React中useEffect多次调用引发API重复请求问题排查

问题分析与解决方案

1. 最核心的原因:React 18 开发环境的 StrictMode

React 18 在开发模式下的StrictMode会刻意重复调用组件渲染、useEffect等方法,目的是帮你提前排查潜在的副作用问题(比如未清理的定时器、重复订阅)。这就是你看到组件渲染5-6次、useEffect执行2次的主要原因——生产环境下不会出现这个行为。

你可以打开index.js核对,代码大概率是这样的:

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

如果想临时验证这个结论,可以暂时去掉<React.StrictMode>标签,开发环境下useEffect就只会执行一次了。不过不建议长期移除,StrictMode能帮你发现很多隐藏问题。

2. 解决API重复请求的问题

哪怕StrictMode导致useEffect执行两次,我们也能通过逻辑控制让API只发起一次,避免重复查询数据库:

方案一:在Redux Action里做前置判断

修改fetchSeries action,先检查Redux中是否已有目标数据,有就直接返回,不发起请求:

// SeriesActions.js
export const fetchSeries = (userId, companyId) => async (dispatch, getState) => {
  // 先获取当前状态中的剧集数据
  const existingSeries = getState().seriesList.series;
  // 如果已有有效数据,直接终止逻辑
  if (existingSeries && existingSeries.length > 0) {
    return;
  }
  // 无数据时再发起API请求
  try {
    const res = await axios.get(`/api/series?userId=${userId}&companyId=${companyId}`);
    dispatch({ type: 'FETCH_SERIES_SUCCESS', payload: res.data });
  } catch (err) {
    dispatch({ type: 'FETCH_SERIES_FAILURE', payload: err.message });
  }
};

方案二:在组件内加请求标记

在组件里新增一个状态,标记是否已经发起过请求,避免重复触发:

const MyList = () => {
  const [isSeriesOpen, setIsSeriesOpen] = useState(false);
  const [hasRequested, setHasRequested] = useState(false); // 新增请求标记
  const dispatch = useDispatch();
  const series = useSelector((state) => state.seriesList.series);
  const userId = useSelector((state) => state.auth.user_id);
  const companyId = 3;

  console.log("Log from the component");

  useEffect(() => {
    // 已请求过或userId不存在时,直接返回
    if (hasRequested || !userId) return;
    dispatch(fetchSeries(userId, companyId));
    setHasRequested(true);
    console.log("Log for Effect called");
  }, [dispatch, userId, hasRequested]); // 补充依赖项


  return (
    <div>
      <p> Test Content</p>
    </div>
  );
};

3. 修复useEffect的依赖项漏洞

你当前的useEffect依赖数组只有dispatch,但回调里用到了userId和companyId——这会导致闭包问题:比如userId初始是undefined(用户未登录时),后来变为真实值,useEffect不会重新执行,请求会用旧的userId发起。

必须把用到的变量都加入依赖数组:

useEffect(() => {
  // 先判断userId是否有效,避免无效请求
  if (!userId) return;
  dispatch(fetchSeries(userId, companyId));
  console.log("Log for Effect called");
}, [dispatch, userId, companyId]); // 补充缺失的依赖

4. 关于组件多次渲染

组件渲染5-6次是正常现象,触发场景包括:

  • 组件初始渲染
  • userId从undefined变为真实值时的重渲染
  • fetchSeries请求完成后,Redux的seriesList.series更新触发的重渲染
  • 父组件状态变化带来的子组件重渲染

如果想减少不必要的重渲染,可以用React.memo包裹组件,或者在useSelector里启用浅比较:

// 导入浅比较工具
import { shallowEqual } from 'react-redux';

// 使用浅比较避免无意义重渲染
const series = useSelector((state) => state.seriesList.series, shallowEqual);
const userId = useSelector((state) => state.auth.user_id, shallowEqual);

// 用React.memo包裹组件,减少父组件无关变化导致的重渲染
export default React.memo(MyList);

内容的提问来源于stack exchange,提问作者user2742581

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 23:30:55