React中useEffect循环执行致API请求过多,如何使其仅执行一次?
React中useEffect重复执行导致API请求过多的解决方案
我的React应用里,useEffect出现持续循环执行的情况,导致向API发送了大量重复请求。需要修改代码让useEffect在获取数据后仅执行一次。
我的代码
import React from 'react'; import { useState, useEffect } from "react"; function App() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null) useEffect(() => { if (!data) ( fetch("http://localhost:4500/coingecko") .then(response => { if (response.ok) { return response.json() } throw response; }) .then(data => { setData(data) }) .catch(error => { console.error("Error fetching data: ", error) setError(error) }) .finally(() => { setLoading(false) })) }, []) //console.log(data) const uniswap = data&&data[0].tickers?.find(donne => donne.trade_url === 'https://app.uniswap.org/#/swap?inputCurrency=0x2260fac5e5542a773aa44fbcfedf7c193bc2c599&outputCurrency=ETH') const uniprice = uniswap?.converted_last?.usd const sushiswap = data&&data[2].tickers?.find(donne => donne.trade_url === 'https://app.sushi.com/swap?inputCurrency=0x2260fac5e5542a773aa44fbcfedf7c193bc2c599&outputCurrency=ETH') const sushiprice = sushiswap?.converted_last?.usd const curvefinance = data&&data[3].tickers?.find(donne => donne.base === 'DAI') const curveprice = curvefinance?.converted_last?.usd const quickswap = data&&data[4].tickers?.find(donne => donne.trade_url === 'https://quickswap.exchange/#/swapinputCurrency=0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270&outputCurrency=0xbbba073c31bf03b8acf7c28ef0738decf3695683') const quickprice = quickswap?.converted_last?.usd console.log(uniprice) console.log(sushiprice) console.log(curveprice) console.log(quickprice) return ( <> {uniprice} </> ) } export default App
控制台情况
控制台多次打印价格数值,说明组件重复渲染,且API请求被多次触发。
问题分析
你当前的useEffect已经设置了空依赖数组[],理论上应该只在组件挂载时执行一次,但出现重复请求的原因主要有两点:
- 多余的
if (!data)判断:空依赖数组已经保证useEffect仅执行一次,初始状态下data就是null,这个判断完全多余,反而容易引发逻辑误解。 - 语法不规范:
if (!data)后使用圆括号()而非花括号{},虽然语法上可行,但不符合常规代码规范,后续维护容易出错。
解决方案
方案1:简化useEffect逻辑(最直接)
直接移除if (!data)判断,同时把圆括号改为标准花括号,空依赖数组会保证useEffect仅在组件挂载时执行一次:
useEffect(() => { fetch("http://localhost:4500/coingecko") .then(response => { if (response.ok) { return response.json() } throw response; }) .then(data => { setData(data) }) .catch(error => { console.error("Error fetching data: ", error) setError(error) }) .finally(() => { setLoading(false) }) }, [])
方案2:添加请求锁(应对组件重复挂载场景)
如果App组件存在重复挂载的情况(比如被路由或父组件频繁渲染),可以添加一个状态变量标记请求是否已发起,彻底避免重复请求:
const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [requestSent, setRequestSent] = useState(false); // 请求状态锁 useEffect(() => { if (!requestSent) { setRequestSent(true); // 标记已发起请求 fetch("http://localhost:4500/coingecko") .then(response => { if (response.ok) { return response.json() } throw response; }) .then(data => { setData(data) }) .catch(error => { console.error("Error fetching data: ", error) setError(error) }) .finally(() => { setLoading(false) }) } }, [requestSent])
方案3:添加请求清理(提升代码健壮性)
虽然不会直接解决循环问题,但可以避免组件卸载时的请求泄漏,让代码更可靠:
useEffect(() => { const controller = new AbortController(); const signal = controller.signal; fetch("http://localhost:4500/coingecko", { signal }) .then(response => { if (response.ok) { return response.json() } throw response; }) .then(data => { setData(data) }) .catch(error => { if (error.name !== 'AbortError') { console.error("Error fetching data: ", error) setError(error) } }) .finally(() => { setLoading(false) }) // 组件卸载时取消未完成的请求 return () => controller.abort(); }, [])
内容的提问来源于stack exchange,提问作者Usual Token
相关产品推荐
相关产品推荐

