页面加载时React代码重复获取相同数据的问题求助
React商品列表首次加载重复获取相同数据问题
我正在学习React,开发一个商品列表的数据获取功能。预期首次加载时获取2条数据,点击按钮再加载2条更多数据,但目前页面加载时会重复获取相同的2条数据。
import { useEffect, useState, Fragment } from 'react'; import './style.css'; export default function LoadMoreData() { const [loading, setLoading] = useState(false); const [products, setProducts] = useState([]); const [count, setCount] = useState(0); const [disableButton, setDisableButton] = useState(false); const loadLimit = 10; let limit = 2; async function fetchProducts() { const dataUrl = `https://dummyjson.com/products?limit=${limit}&skip=${ count === 0 ? 0 : count * limit }`; try { setLoading(true); const response = await fetch(dataUrl); const result = await response.json(); if (result && result.products && result.products.length) { setProducts((prevData) => [...prevData, ...result.products]); setLoading(false); } console.log(result); } catch (e) { console.log(e); setLoading(false); } } useEffect(() => { fetchProducts(); }, [count]); useEffect(() => { if (products && products.length === loadLimit) setDisableButton(true); }, [products]); if (loading) { return <div>Loading data! Please wait.</div>; } return ( <Fragment> <div className='load-more-container'> <div className='product-container'> {products && products.length ? products.map((item) => ( <div className='product' key={item.id}> <img src={item.thumbnail} alt={item.title} /> <p>{item.title}</p> <span>$ {item.price}</span> </div> )) : null} </div> <div className='button-container'> <button disabled={disableButton} onClick={() => setCount(count + 1)}> Load more products </button> {disableButton ? ( <p>You have reached to {loadLimit} products!.</p> ) : null} </div> </div> </Fragment> ); }
我曾猜测问题出在将异步函数放在useEffect外部,但即使将其移入useEffect,问题仍未解决。能否有人提示我问题可能出在哪里?谢谢!
问题原因及解决方案
1. 核心问题分析
重复请求的根源有两点:
- 请求参数逻辑依赖不稳定:你用
count计算skip值,但开发环境下React的Strict Mode会触发两次组件挂载,此时count初始值为0,两次useEffect都会发起skip=0的请求,导致获取相同数据。 - 无重复请求拦截机制:当
loading状态未更新完成时,若再次触发请求,会重复调用接口。
2. 具体修复步骤
(1)用现有数据长度计算skip
直接用products.length作为skip值,因为每次加载limit条数据,下一次的起始位置就是当前已加载总数,逻辑更可靠,还能避免count带来的闭包问题:
const dataUrl = `https://dummyjson.com/products?limit=${limit}&skip=${products.length}`;
(2)添加重复请求拦截
在fetchProducts开头判断,若当前处于加载状态则直接返回,避免重复发起请求:
async function fetchProducts() { if (loading) return; // 拦截重复请求 // 原有请求逻辑... }
(3)优化状态依赖(可选)
count状态可以完全移除,直接在按钮点击时调用fetchProducts即可,同时调整组件挂载时的请求触发逻辑:
// 组件挂载时只请求一次 useEffect(() => { fetchProducts(); }, []); // 按钮点击直接调用请求函数 <button disabled={disableButton || loading} onClick={fetchProducts}> Load more products </button>
修复后的完整代码
import { useEffect, useState, Fragment } from 'react'; import './style.css'; export default function LoadMoreData() { const [loading, setLoading] = useState(false); const [products, setProducts] = useState([]); const [disableButton, setDisableButton] = useState(false); const loadLimit = 10; const limit = 2; // 用const替代let,避免意外修改 async function fetchProducts() { if (loading) return; // 拦截重复请求 const dataUrl = `https://dummyjson.com/products?limit=${limit}&skip=${products.length}`; try { setLoading(true); const response = await fetch(dataUrl); const result = await response.json(); if (result?.products?.length) { setProducts(prev => [...prev, ...result.products]); } else { setDisableButton(true); // 无更多数据时禁用按钮 } } catch (e) { console.error(e); } finally { setLoading(false); // 无论请求成败都关闭loading } } useEffect(() => { fetchProducts(); }, []); useEffect(() => { if (products.length >= loadLimit) { setDisableButton(true); } }, [products]); if (loading) { return <div>Loading data! Please wait.</div>; } return ( <Fragment> <div className='load-more-container'> <div className='product-container'> {products.map(item => ( <div className='product' key={item.id}> <img src={item.thumbnail} alt={item.title} /> <p>{item.title}</p> <span>$ {item.price}</span> </div> ))} </div> <div className='button-container'> <button disabled={disableButton || loading} onClick={fetchProducts}> Load more products </button> {disableButton && <p>You have reached {loadLimit} products!</p>} </div> </div> </Fragment> ); }
内容的提问来源于stack exchange,提问作者haru
相关产品推荐
相关产品推荐

