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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 01:50:24