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

React中useEffect引发Console日志重复问题求助

解决React组件中API请求重复执行的问题

我在React组件里模拟向http://localhost:1777/api/test发起POST请求,想把响应存入对象后在控制台查看,但发现console.log(simulatedResponse)执行了两次,控制台输出重复的对象。调整useEffect后问题仍没解决,代码如下:

import React, { useEffect, useState } from 'react';
import { MyEditor } from '@Editor/my-editor';

function MedEditor() {
  const URL = 'http://keycloak:1901/auth/realms/myEditor/protocol/openid-connect/token';
  const [token, setToken] = useState(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    const getToken = async () => {
      const params = new URLSearchParams();

      params.append('grant_type', 'client_credentials');
      params.append('client_id', 'myEditor_cli_client');
      params.append('client_secret', 'myEditor_cli_client_secret');

      const response = await fetch(URL, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: params,
      });

      const data = await response.json();
      setToken(data.access_token);
      setIsLoading(false);
    };
    getToken();
  }, []);

  useEffect(() => {
    if (token) {
      simulateApiRequest();
    }
  }, [token]);

  const simulateApiRequest = async () => {
    const postData = {
      data: {
        correlationId: 123,
        subjectId: "01",
        text: "test text"
      }
    };

    const response = await fetch('http://localhost:1777/api/test', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + token,
      },
      body: JSON.stringify(postData),
    });

    if (response.ok) {
      const simulatedResponse = await response.json();
      console.log(simulatedResponse); // 控制台打印响应结果
      // 此处可按需使用响应数据
    } else {
      console.error('API请求失败:', response.status, response.statusText);
    }
  };

  return (
    <div className="editor-container">
      {isLoading ? (
        <p>加载中...</p>
      ) : token ? (
        <MedNoteEditor tokenProvider={{ getToken: () => token }} />
      ) : (
        <p>无可用token。</p>
      )}
    </div>
  );
}

export default MedEditor;

问题原因

最常见的触发因素是React 18+的严格模式:在开发环境下,严格模式会刻意重复调用useEffect等副作用函数,用来检测代码中潜在的重复执行类bug。另外,token状态更新后,依赖它的useEffect会触发,如果组件因其他原因重渲染,也可能导致二次执行,但结合代码来看,严格模式是主要原因。

解决方案

1. 临时关闭严格模式(开发环境用)

在项目根组件(比如index.js)里移除<React.StrictMode>标签,就能避免开发环境下的重复执行。但不建议长期关闭,严格模式能帮你提前发现很多潜在问题。

2. 用状态标记避免重复请求

新增一个hasFetched状态,标记请求是否已执行,确保只发起一次请求:

import React, { useEffect, useState } from 'react';
import { MyEditor } from '@Editor/my-editor';

function MedEditor() {
  const URL = 'http://keycloak:1901/auth/realms/myEditor/protocol/openid-connect/token';
  const [token, setToken] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [hasFetched, setHasFetched] = useState(false); // 新增请求标记状态

  useEffect(() => {
    const getToken = async () => {
      const params = new URLSearchParams();

      params.append('grant_type', 'client_credentials');
      params.append('client_id', 'myEditor_cli_client');
      params.append('client_secret', 'myEditor_cli_client_secret');

      const response = await fetch(URL, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: params,
      });

      const data = await response.json();
      setToken(data.access_token);
      setIsLoading(false);
    };
    getToken();
  }, []);

  useEffect(() => {
    // 仅当token存在且未发起过请求时执行
    if (token && !hasFetched) {
      simulateApiRequest();
      setHasFetched(true); // 标记请求已完成
    }
  }, [token, hasFetched]);

  const simulateApiRequest = async () => {
    const postData = {
      data: {
        correlationId: 123,
        subjectId: "01",
        text: "test text"
      }
    };

    const response = await fetch('http://localhost:1777/api/test', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + token,
      },
      body: JSON.stringify(postData),
    });

    if (response.ok) {
      const simulatedResponse = await response.json();
      console.log(simulatedResponse);
    } else {
      console.error('API请求失败:', response.status, response.statusText);
    }
  };

  return (
    <div className="editor-container">
      {isLoading ? (
        <p>加载中...</p>
      ) : token ? (
        <MedNoteEditor tokenProvider={{ getToken: () => token }} />
      ) : (
        <p>无可用token。</p>
      )}
    </div>
  );
}

export default MedEditor;

3. 合并副作用逻辑

把获取token和发起测试请求合并到同一个useEffect,减少状态依赖触发的二次执行:

import React, { useEffect, useState } from 'react';
import { MyEditor } from '@Editor/my-editor';

function MedEditor() {
  const URL = 'http://keycloak:1901/auth/realms/myEditor/protocol/openid-connect/token';
  const [token, setToken] = useState(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    const fetchAllData = async () => {
      // 第一步:获取token
      const params = new URLSearchParams();
      params.append('grant_type', 'client_credentials');
      params.append('client_id', 'myEditor_cli_client');
      params.append('client_secret', 'myEditor_cli_client_secret');

      const tokenResponse = await fetch(URL, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: params,
      });

      const tokenData = await tokenResponse.json();
      const accessToken = tokenData.access_token;
      setToken(accessToken);
      setIsLoading(false);

      // 第二步:发起测试请求
      const postData = {
        data: {
          correlationId: 123,
          subjectId: "01",
          text: "test text"
        }
      };

      const testResponse = await fetch('http://localhost:1777/api/test', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer ' + accessToken,
        },
        body: JSON.stringify(postData),
      });

      if (testResponse.ok) {
        const simulatedResponse = await testResponse.json();
        console.log(simulatedResponse);
      } else {
        console.error('API请求失败:', testResponse.status, testResponse.statusText);
      }
    };

    fetchAllData();
  }, []);

  return (
    <div className="editor-container">
      {isLoading ? (
        <p>加载中...</p>
      ) : token ? (
        <MedNoteEditor tokenProvider={{ getToken: () => token }} />
      ) : (
        <p>无可用token。</p>
      )}
    </div>
  );
}

export default MedEditor;

这种方式下,所有逻辑只在组件挂载时执行一次,既避免了token状态更新触发的二次执行,也能规避严格模式的重复调用影响。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 01:25:05