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判断逻辑导致重复执行风险。
解决方法:
- 删除清理函数中清空
gecko.data的逻辑; - 用
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
相关产品推荐
相关产品推荐

