如何在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
相关产品推荐
相关产品推荐

