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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 22:07:51