如何在React中正确实现isLoading逻辑?解决图片加载不同步问题
问题:优化Loading逻辑,实现图片同步显示
当前数据请求完成后就隐藏Loading,但图片会逐个加载显示,导致不同步。现有代码如下:
数据请求逻辑:
const [isLoading, setIsLoading] = useState(false) useEffect(()=>{ setIsLoading(true) fetch(url).then(res=>res.json()).then(data=>{ setPhoto(data) setIsLoading(false) }) },[url])
渲染逻辑:
return ( <div className="App"> <header>.....</header> {isLoading?<p className={'text-5xl mt-11'}>Loading...</p>:<div className={'flex flex-wrap space-x-2 justify-around '}> {photos?photos.hits.map((photo, index)=>{ return <img className={'m-5 h-80 rounded-2xl'} onClick={handleClickOnPicture} key={photo.webformatURL} src={photo.webformatURL}/> }):""} </div> </div> )
解决方案
核心问题分析
现有逻辑的问题是:数据请求完成就关闭Loading,但此时图片仅拿到URL,还在浏览器异步加载中,所以会出现图片陆续弹出的情况,无法同步显示。要解决这个问题,需要等所有图片加载完成后再显示图片容器。
方案一:预加载所有图片后再显示
直接在数据请求完成后,先把所有图片加载到浏览器缓存,等全部加载完再关闭Loading并渲染图片。
修改后的请求逻辑:
const [isLoading, setIsLoading] = useState(false); const [photos, setPhotos] = useState(null); useEffect(() => { setIsLoading(true); fetch(url) .then(res => res.json()) .then(data => { // 为每张图片创建加载Promise const imageLoadPromises = data.hits.map(photo => { return new Promise((resolve, reject) => { const img = new Image(); img.src = photo.webformatURL; // 加载成功或失败都resolve,避免卡住 img.onload = resolve; img.onerror = resolve; }); }); // 等待所有图片加载完成 Promise.all(imageLoadPromises) .then(() => { setPhotos(data); setIsLoading(false); }) .catch(err => { console.error('图片预加载出错:', err); setPhotos(data); setIsLoading(false); }); }) .catch(err => { console.error('数据请求失败:', err); setIsLoading(false); }); }, [url]);
渲染逻辑保持不变即可,此时所有图片已缓存,会同步显示。
方案二:分阶段显示(数据加载→图片加载→显示)
如果不想让用户等太久的Loading,可以先关闭数据Loading,显示图片加载中的过渡状态,等所有图片加载完再显示图片容器。
修改后的代码:
const [isLoading, setIsLoading] = useState(false); const [photos, setPhotos] = useState(null); const [allImagesLoaded, setAllImagesLoaded] = useState(false); // 数据请求逻辑 useEffect(() => { setIsLoading(true); setAllImagesLoaded(false); // 新请求重置图片加载状态 fetch(url) .then(res => res.json()) .then(data => { setPhotos(data); setIsLoading(false); }) .catch(err => { console.error('数据请求失败:', err); setIsLoading(false); }); }, [url]); // 监听图片加载完成状态 useEffect(() => { if (!photos) return; let loadedCount = 0; const total = photos.hits.length; const checkAllLoaded = () => { loadedCount++; if (loadedCount === total) { setAllImagesLoaded(true); } }; // 预加载每张图片并计数 photos.hits.forEach(photo => { const img = new Image(); img.src = photo.webformatURL; img.onload = checkAllLoaded; img.onerror = checkAllLoaded; }); }, [photos]);
渲染逻辑:
return ( <div className="App"> <header>.....</header> {/* 数据加载中显示 */} {isLoading && <p className="text-5xl mt-11">Loading...</p>} {/* 数据加载完成,图片加载中显示 */} {!isLoading && !allImagesLoaded && ( <p className="text-5xl mt-11">加载图片中...</p> )} {/* 所有图片加载完成后显示容器 */} {allImagesLoaded && ( <div className="flex flex-wrap space-x-2 justify-around"> {photos.hits.map(photo => ( <img className="m-5 h-80 rounded-2xl" onClick={handleClickOnPicture} key={photo.webformatURL} src={photo.webformatURL} /> ))} </div> )} </div> );
方案对比
- 方案一:用户看到的Loading时间更长,但图片会一次性全部显示,体验更统一,适合图片数量较少的场景。
- 方案二:用户能更快看到页面加载的反馈,分阶段显示状态,适合图片数量较多的场景。
内容的提问来源于stack exchange,提问作者Fuad Rustamzade
相关产品推荐
相关产品推荐

