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
相关产品推荐
相关产品推荐

