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

React应用中API调用被重复多次触发该如何解决?

问题诱因

你遇到的重复打印、重复触发请求的问题,核心原因分两类:

  • React 18 开发环境严格模式(Strict Mode)的默认校验行为:这是最主要的诱因。React 18 版本起,开发环境下严格模式会主动执行两次组件挂载、卸载的流程,用来提前检测副作用不规范、内存泄漏等代码问题。这个双重执行逻辑只会在开发环境生效,生产环境打包后不会出现。
  • 代码本身的写法问题放大了重复执行的表现:你把console.log(response)直接写在组件函数顶层,组件每次重渲染、重挂载都会执行这行代码;同时你的请求逻辑没有做防重、没有做清理,副作用触发时就会重复发起请求、重复更新state,进一步触发更多次重渲染。

另外你的代码里还有一个容易被忽略的语法错误:fetch请求配置的请求头字段名是headers(带s),你写的是header,会导致请求头不生效,可能引发接口返回异常。

你提供的单次点击按钮后的控制台输出截图如下:
控制台重复打印响应结果截图

修复方案
  • 不建议直接关闭严格模式
    严格模式的双重校验是React官方提供的代码质量检测能力,能帮你提前定位很多隐蔽问题,为了消掉重复日志关掉严格模式属于因小失大。

  • 修正请求逻辑,增加防重和清理机制
    不管是按钮触发请求还是初始化自动拉取,都要做重复请求拦截,useEffect里的请求要加取消逻辑,避免组件重执行时触发无效请求:

    修正后的按钮触发请求代码:

import { useState, useRef, useEffect } from 'react';

function App() {
  const [ response , setResponse ] = useState();
  const [ btnText , setbtnText ] = useState('Get Data');
  const [ details , setDetails ] = useState();
  // 用ref做请求锁,防止重复点击触发重复请求
  const requestLock = useRef(false);
  
  async function fetchData() {
    if (requestLock.current) return;
    requestLock.current = true;
    setbtnText('Loading...');
    try {
      // 修正headers字段名,统一async/await写法避免链式调用冗余
      const res1 = await fetch('url hidden' , {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
      });
      const data1 = await res1.json();
      setResponse(data1);

      const res2 = await fetch('url hidden' , {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
      });
      const data2 = await res2.json();
      setDetails(data2);

    } catch (error) {
      console.log(error);
    } finally {
      requestLock.current = false;
      setbtnText('Get Data');
    };
  }

  // 不要在组件顶层直接打日志,放到useEffect里监听对应值变化再打印,避免无意义的日志输出
  useEffect(() => {
    console.log(response);
  }, [response])

  return (
    <div className="container">
      <header className='header'>
        <button onClick={fetchData} disabled={requestLock.current}>{btnText}</button>
      </header>
      <Summary response={response} details={details} />
    </div>
  );
}

修正后的初始化自动拉取(useEffect版本)代码,增加请求取消逻辑,适配严格模式的双重执行:

useEffect(() => {
  // 用AbortController做请求取消,避免重复请求和组件卸载后更新state的警告
  const controller = new AbortController();
  const signal = controller.signal;

  const fetchInitData = async () => {
    try {
      const [res1Data, res2Data] = await Promise.all([
        fetch('url hidden' , {
          method: 'POST',
          headers: {'Content-Type': 'application/json'},
          signal
        }).then(res => res.json()),
        fetch('url hidden' , {
          method: 'POST',
          headers: {'Content-Type': 'application/json'},
          signal
        }).then(res => res.json()),
      ]);
      // 确认请求没被取消再更新state
      if (!signal.aborted) {
        setResponse(res1Data);
        setDetails(res2Data);
      }
    } catch(err) {
      // 主动取消的请求不需要打错误日志
      if (err.name !== 'AbortError') {
        console.log(err);
      }
    }
  }

  fetchInitData();
  //  effect重执行/组件卸载时取消上一次未完成的请求
  return () => controller.abort();
} , [])
  • 用网络面板判断真实请求次数
    不要靠控制台的state日志判断请求是否重复,打开浏览器开发者工具的「网络」面板,查看实际的接口请求记录:加了上述清理逻辑后,严格模式下触发的多余请求会被标记为已取消,不会真的打到你的DynamoDB后端,生产环境构建后只会存在单次有效请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 17:06:26