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

React useEffect API请求问题:清理函数失效与Promise数组异常

React API 请求与 useEffect 问题排查

问题描述

我是编程爱好者,刚学完React基础课程,正在学习API相关知识。按照讲师建议在useEffect里做API请求(外部操作属于副作用),现在遇到三个问题:

  • 第一个useEffect的清理函数本该把count状态设为1,但组件挂载、卸载再挂载后还是会进入if判断块;
  • 第二个useEffect里,fetch的then块能拿到数据对象,但finalCoinArr最终是Promise数组,没法正确存入状态;
  • 第二个useEffect的清理函数会把gecko.data设为空数组,能不能避免清空数组,同时首次请求后不再重复调用API?

相关代码

function App() {
  const [unsplash, setUnsplash] = React.useState({data:{urls:{full:'', regular:''},user:{name:'',portfolio_url:''}}})
  const [gecko, setGecko] = React.useState({data:[]})
  const [count, setCount] = React.useState(0);
const scrimbaUrl = `https://apis.scrimba.com/unsplash/photos/random?orientation=landscape&query=nature`
const coinGeckoUrl = `https://api.coingecko.com/api/v3/coins/`
  const coinArr = [{name:'bitcoin', image:{small:'https://assets.coingecko.com/coins/images/1/small/bitcoin.png?1547033579'}},{name:'dogecoin', image:{small:"https://assets.coingecko.com/coins/images/5/small/dogecoin.png?1547792256"}},{name:'ethereum', image:{small:"https://assets.coingecko.com/coins/images/279/small/ethereum.png?1595348880"}}, {name:'litecoin', image:{small:"https://assets.coingecko.com/coins/images/2/small/litecoin.png?1547033580"}}]
React.useEffect(()=>{
    if(count === 0){
      fetch(scrimbaUrl)
      .then(res=>res.json())
      .then(data=>{
        // console.log('called fetch')
        setUnsplash((prevState)=>{
          return {...prevState, data:data}
        })
      })
      .catch((err)=>{
        console.log(`ScrimbaE:${err}`)
        let defaultBackground = 'https://images.unsplash.com/photo-1503264116251-35a269479413?crop=entropy&cs=tinysrgb&fm=jpg&ixid=MnwxNDI0NzB8MHwxfHJhbmRvbXx8fHx8fHx8fDE2NzExNTg5MTE&ixlib=rb-4.0.3&q=80'
        let defaultName = 'Aperture Vintage'
        let defaultPortfolio = 'https://creativemarket.com/PedroCS?u=PedroCS'
        let defaultUnsplash = {urls:{full:defaultBackground}, user:{name:defaultName, portfolio_url:defaultPortfolio}}
        setUnsplash((prevState)=>{
          return {...prevState, data:defaultUnsplash}
        })
      })
    }
    
      return ()=>{
        setCount((prevCount)=>prevCount+1)
      }
  },[])
React.useEffect(()=>{
    if(count === 0){
      let finalCoinArr = coinArr.map((ele)=>{
       return fetch(`${coinGeckoUrl}${ele.name}`)
          .then(res=>res.json())
          .then(data=>{
            return data
          })
      })
      setGecko(()=>{
        return {data:finalCoinArr}
      })
      console.log(finalCoinArr)
    }
    
    return ()=>{
      setGecko(()=>{
        return {data:[]}
      })
    }
    
  },[])
  // console.log(gecko)

  /*Returned */
// {data: Array(4)}
// data: Array(4)
// 0: Promise {<fulfilled>: {…}}
// 1: Promise {<fulfilled>: {…}}
// 2: Promise {<fulfilled>: {…}}
// 3: Promise {<fulfilled>: {…}}
// length: 4

问题解决

问题1:清理函数修改count后,组件重挂载仍进入if判断

原因:count是组件内部状态,组件卸载后状态会被销毁,重新挂载时count会回到初始值0,清理函数修改的count仅在当前组件实例有效,卸载后即失效。

解决方法:
不需要用count控制请求执行,改用useRef存储实例级标记,确保组件生命周期内仅执行一次:

const hasFetchedUnsplash = React.useRef(false);

React.useEffect(()=>{
  if(!hasFetchedUnsplash.current){
    // 执行原fetch请求逻辑
    hasFetchedUnsplash.current = true;
  }
},[])

如果需要页面刷新后也避免重复请求,可结合localStorage存储标记:

React.useEffect(()=>{
  const hasFetched = localStorage.getItem('hasFetchedUnsplash');
  if(!hasFetched){
    // 执行原fetch请求逻辑
    localStorage.setItem('hasFetchedUnsplash', 'true');
  }
},[])

问题2:finalCoinArr是Promise数组,无法正确存入状态

原因:map中的fetch是异步操作,返回的是Promise对象,因此finalCoinArr是Promise数组,而非实际响应数据。

解决方法:
使用Promise.all等待所有请求完成后,再将结果存入状态:

React.useEffect(()=>{
  const fetchCoins = async () => {
    try {
      const promises = coinArr.map(ele => 
        fetch(`${coinGeckoUrl}${ele.name}`).then(res => res.json())
      );
      const finalCoinArr = await Promise.all(promises);
      setGecko({ data: finalCoinArr });
    } catch(err) {
      console.log(`CoinGeckoE:${err}`);
    }
  };

  fetchCoins();
},[])

问题3:避免清理函数清空gecko.data,且首次请求后不再重复调用

原因:当前清理函数强制在组件卸载时清空状态;同时依赖项设置和count判断逻辑导致重复执行风险。

解决方法:

  1. 删除清理函数中清空gecko.data的逻辑;
  2. 用useRef标记请求状态,确保仅执行一次:
const hasFetchedCoins = React.useRef(false);

React.useEffect(()=>{
  if(hasFetchedCoins.current) return;
  
  const fetchCoins = async () => {
    try {
      const promises = coinArr.map(ele => 
        fetch(`${coinGeckoUrl}${ele.name}`).then(res => res.json())
      );
      const finalCoinArr = await Promise.all(promises);
      setGecko({ data: finalCoinArr });
      hasFetchedCoins.current = true;
    } catch(err) {
      console.log(`CoinGeckoE:${err}`);
    }
  };

  fetchCoins();
  
  // 无需清空状态,清理函数留空或直接删除
  return () => {};
},[])

若需页面刷新后保留数据,可将gecko.data存入localStorage,初始化时读取:

const [gecko, setGecko] = React.useState({
  data: JSON.parse(localStorage.getItem('coinData')) || []
});

// 请求完成后同步存储到localStorage
setGecko({ data: finalCoinArr });
localStorage.setItem('coinData', JSON.stringify(finalCoinArr));

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 10:45:43