React图片上传增删时重复渲染图片的性能优化问题
解决React图片上传组件删除时剩余图片重复加载的问题
问题根源
每次组件重新渲染(比如删除图片触发setSelectedFiles),都会对剩余的fileObject重新调用URL.createObjectURL(),生成全新的Blob URL。浏览器会把这个新URL当成新资源重新请求加载,导致你看到的重复请求。同时,频繁创建Blob URL不清理还会造成内存泄漏。
解决方案:缓存Blob URL并及时清理
核心思路是在文件添加时就生成Blob URL并缓存到state中,而不是每次渲染时动态生成。同时在文件被删除或组件卸载时,主动释放这些URL。
修改后的代码
function App() { const [selectedFiles, setSelectedFiles] = React.useState([]); function GenerateGuid() { return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c => { const randomValue = crypto.getRandomValues(new Uint8Array(1))[0]; const shiftedValue = (randomValue & 15) >> (+c / 4); return shiftedValue.toString(16); }); } // 清理Blob URL的工具函数 const revokeBlobUrl = (url) => { if (url) { URL.revokeObjectURL(url); } }; const handleImageDelete = (id) => { // 找到要删除的文件,先清理它的Blob URL const fileToDelete = selectedFiles.find(file => file.id === id); revokeBlobUrl(fileToDelete.blobUrl); const updatedFiles = selectedFiles.filter((file) => file.id !== id); setSelectedFiles(updatedFiles); }; const handleChange = (e) => { const files = Array.from(e.target.files); const filesObject = files.map(file => { const blobUrl = URL.createObjectURL(file); return { id: GenerateGuid(), fileObject: file, blobUrl // 缓存生成的Blob URL }; }); setSelectedFiles(prev => [...prev, ...filesObject]); }; // 组件卸载时清理所有剩余的Blob URL React.useEffect(() => { return () => { selectedFiles.forEach(file => revokeBlobUrl(file.blobUrl)); }; }, [selectedFiles]); return ( <div className='App'> <h1>Hello React.</h1> <h2>Start editing to see some magic happen!</h2> <input accept="image/png, image/gif, image/jpeg" type="file" onChange={handleChange} multiple /> {selectedFiles.map((file) => ( <img key={file.id} onClick={() => handleImageDelete(file.id)} style={{ height: "5rem", backgroundColor: "black", margin: "0 0.5rem" }} src={file.blobUrl} // 直接使用缓存的URL alt={"training spot"} /> ))} </div> ); } ReactDOM.render(<App />, document.getElementById('root'));
关键修改点
- 缓存Blob URL:在
handleChange中处理文件时,一次性生成blobUrl并存入state对象,后续渲染直接复用这个URL,避免重复生成。 - 清理Blob URL:删除文件时,先调用
URL.revokeObjectURL释放对应URL的内存;组件卸载时遍历所有文件清理剩余URL,防止内存泄漏。 - 使用函数式更新state:
setSelectedFiles(prev => [...prev, ...filesObject]),避免依赖当前state的快照,确保更新的准确性。
额外优化建议
如果图片数量较多,可以把单个图片拆分为独立的ImagePreview组件,并用React.memo包裹,进一步避免不必要的重渲染:
const ImagePreview = React.memo(({ file, onDelete }) => { return ( <img onClick={() => onDelete(file.id)} style={{ height: "5rem", backgroundColor: "black", margin: "0 0.5rem" }} src={file.blobUrl} alt={"training spot"} /> ); });
在App组件中替换为:
{selectedFiles.map((file) => ( <ImagePreview key={file.id} file={file} onDelete={handleImageDelete} /> ))}
内容的提问来源于stack exchange,提问作者Sebastian Meckovski
相关产品推荐
相关产品推荐

