在React useEffect中创建与释放Blob的正确方式及无限循环解决
问题分析与解决
原代码触发无限循环的核心原因是useEffect的依赖数组包含了src:每次setSrc(url)更新src状态后,useEffect会因为依赖变化重新执行,再次调用getImage、创建新的ObjectURL并更新src,形成无限循环。同时清理函数依赖src的闭包值,可能无法正确释放最新的URL。
正确实现方式
import { useEffect, useState, type ReactElement } from 'react'; async function getImage(): Promise<Blob> { const res = await fetch('https://picsum.photos/200'); const blob = await res.blob(); return blob; } export default function Image(): ReactElement { const [src, setSrc] = useState<string | null>(null); useEffect(() => { let objectUrl: string | null = null; const fetchAndSetImage = async () => { try { const blob = await getImage(); objectUrl = URL.createObjectURL(blob); setSrc(objectUrl); } catch (error) { console.error('图片加载失败:', error); } }; fetchAndSetImage(); // 组件卸载或effect重新执行时清理 return () => { if (objectUrl) { URL.revokeObjectURL(objectUrl); } }; }, []); // 空依赖数组,仅在组件挂载时执行一次 return <img src={src ?? undefined} alt="some image" />; }
关键改动说明
- 清空依赖数组:
[]确保useEffect只在组件挂载时执行一次,避免因src更新触发重复执行。 - 内部保存ObjectURL:在effect内部声明
objectUrl变量,直接捕获创建的URL,避免闭包导致的旧值问题,确保清理时能正确释放资源。 - 异步函数封装:把异步逻辑封装成内部函数,更清晰且方便错误处理。
- 合理的错误处理:用
console.error替代直接throw,避免未捕获异常导致程序崩溃。
最佳实践补充
- 如果需要根据props变化重新加载图片(比如图片地址动态变化),把对应的props加入依赖数组即可,同时清理函数会自动释放旧的URL。
- 始终确保每个
URL.createObjectURL都对应一个URL.revokeObjectURL,避免浏览器内存泄漏。 - 若图片加载是高频操作(比如列表中大量图片),可以考虑缓存Blob或ObjectURL,减少重复请求和资源创建。
内容的提问来源于stack exchange,提问作者Noah
相关产品推荐
相关产品推荐

