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

如何在React获取完S3所有图片后再渲染Amplify StorageImage组件?

解决StorageImage组件加载前显示损坏图片图标的问题

问题描述

我有一个React函数式组件,通过getAllImageFilePathsFromS3Bucket()获取S3存储桶中的所有图片文件路径,存入images字符串数组后,将每个路径映射到Amplify的StorageImage组件的path属性以展示图片。目前功能正常,但UI会先显示约1-2秒的损坏图片图标,之后才成功加载图片。尝试转为类组件使用ComponentDidMount()方法,问题依旧。需要实现仅在图片加载完成后显示StorageImage组件,或阻止该现象。

现有代码

import React, { FC, useState, useEffect } from 'react';
import { list } from '@aws-amplify/storage'
import { StorageImage } from '@aws-amplify/ui-react-storage';

async function getAllImageFilePathsFromS3Bucket() {  
 let filepaths: Array<string> = [];
 const result = await list({ path: 'public/' });
 let listOfS3Items = result.items;
 listOfS3Items.forEach((x) => {
   if (x.size)
     filepaths.push(x.path);
 })
   return filepaths
}

interface PhotosProps { }

const Photos: FC<PhotosProps> = () => {

const [images, setImages] = useState<string[]>([]);

 useEffect(() => {
   getAllImageFilePathsFromS3Bucket().then(x => {
   setImages(x);
  });
 }, []);

return (
     <>
      {
        images.map((file) => {
          return (
            <StorageImage
              width="100%"
              height="100%"
              objectFit="cover"
              objectPosition="50% 50%"
              alt="photo-item"
              path={file} key={file} />
             )
           })
          }   
       </>
       );
 }

export default Photos;

解决方案

方法1:预加载所有图片后再渲染

通过预加载所有图片资源,确保图片加载完成后再渲染StorageImage组件,彻底避免损坏图标出现。

修改后的代码:

import React, { FC, useState, useEffect } from 'react';
import { list, getUrl } from '@aws-amplify/storage'
import { StorageImage } from '@aws-amplify/ui-react-storage';

async function getAllImageFilePathsFromS3Bucket() {  
 let filepaths: Array<string> = [];
 const result = await list({ path: 'public/' });
 let listOfS3Items = result.items;
 listOfS3Items.forEach((x) => {
   if (x.size)
     filepaths.push(x.path);
 })
 return filepaths
}

// 预加载图片的工具函数
async function preloadImages(imagePaths: string[]) {
  const loadPromises = imagePaths.map(async (path) => {
    const { url } = await getUrl({ path });
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.src = url;
      img.onload = resolve;
      img.onerror = reject;
    });
  });
  await Promise.all(loadPromises);
}

interface PhotosProps { }

const Photos: FC<PhotosProps> = () => {
  const [images, setImages] = useState<string[]>([]);
  const [isImagesLoaded, setIsImagesLoaded] = useState(false);

  useEffect(() => {
    const fetchAndPreload = async () => {
      const imagePaths = await getAllImageFilePathsFromS3Bucket();
      await preloadImages(imagePaths);
      setImages(imagePaths);
      setIsImagesLoaded(true);
    };
    fetchAndPreload();
  }, []);

  // 加载完成前显示自定义占位符
  if (!isImagesLoaded) {
    return <div style={{width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center'}}>加载中...</div>;
  }

  return (
    <>
      {images.map((file) => (
        <StorageImage
          width="100%"
          height="100%"
          objectFit="cover"
          objectPosition="50% 50%"
          alt="photo-item"
          path={file} 
          key={file} />
      ))}   
    </>
  );
}

export default Photos;

方法2:为单个图片添加加载状态控制

如果不想预加载所有图片,可以为每个图片单独管理加载状态,加载完成前显示占位符,避免损坏图标暴露。

修改后的代码:

import React, { FC, useState, useEffect } from 'react';
import { list } from '@aws-amplify/storage'
import { StorageImage } from '@aws-amplify/ui-react-storage';

async function getAllImageFilePathsFromS3Bucket() {  
 let filepaths: Array<string> = [];
 const result = await list({ path: 'public/' });
 let listOfS3Items = result.items;
 listOfS3Items.forEach((x) => {
   if (x.size)
     filepaths.push(x.path);
 })
 return filepaths
}

interface PhotosProps { }

const Photos: FC<PhotosProps> = () => {
  const [images, setImages] = useState<string[]>([]);
  // 记录每个图片的加载状态:loading/loaded/error
  const [imageLoadStatus, setImageLoadStatus] = useState<Record<string, 'loading' | 'loaded' | 'error'>>({});

  useEffect(() => {
    getAllImageFilePathsFromS3Bucket().then(x => {
      setImages(x);
      // 初始化所有图片为加载中状态
      const initialStatus = x.reduce((acc, path) => {
        acc[path] = 'loading';
        return acc;
      }, {} as Record<string, 'loading' | 'loaded' | 'error'>);
      setImageLoadStatus(initialStatus);
    });
  }, []);

  const handleImageLoad = (path: string) => {
    setImageLoadStatus(prev => ({ ...prev, [path]: 'loaded' }));
  };

  const handleImageError = (path: string) => {
    setImageLoadStatus(prev => ({ ...prev, [path]: 'error' }));
  };

  return (
    <>
      {images.map((file) => {
        const status = imageLoadStatus[file];
        if (status === 'loading') {
          // 加载中显示灰色占位块
          return <div key={file} style={{width: '100%', height: '100%', backgroundColor: '#f0f0f0'}}></div>;
        }
        if (status === 'error') {
          // 加载失败显示错误提示
          return <div key={file} style={{width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#999'}}>图片加载失败</div>;
        }
        return (
          <StorageImage
            width="100%"
            height="100%"
            objectFit="cover"
            objectPosition="50% 50%"
            alt="photo-item"
            path={file} 
            key={file}
            onLoad={() => handleImageLoad(file)}
            onError={() => handleImageError(file)} />
        );
      })}   
    </>
  );
}

export default Photos;

方法3:使用StorageImage的fallback属性

部分版本的Amplify UI StorageImage支持fallback属性,可直接指定加载过程中显示的占位组件,替换默认的损坏图标:

// 在StorageImage组件中添加fallback属性
<StorageImage
  width="100%"
  height="100%"
  objectFit="cover"
  objectPosition="50% 50%"
  alt="photo-item"
  path={file} 
  key={file}
  fallback={<div style={{width: '100%', height: '100%', backgroundColor: '#f0f0f0'}}></div>} />

内容的提问来源于stack exchange,提问作者Raoul Duke

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 03:35:06