React中使用懒加载图片时,为什么部分图片在滚动时会重复加载?

欧阳司翰 阅读 26

我在做一个图片列表页,用Intersection Observer做懒加载。但滚动时发现第5、6张图片会反复触发加载,明明已经离开可视区又回来了。已经试过react-lazyload和自己写hook,都一样:


function ImageList() {
  const [loaded, setLoaded] = useState({});

  useEffect(() => {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const img = entry.target;
          img.src = img.dataset.src;
          observer.unobserve(img);
          setLoaded(prev => ({...prev, [entry.target.id]: true}));
        }
      });
    }, {rootMargin: '0px', threshold: 0.1});

    document.querySelectorAll('.lazy-img').forEach(el => observer.observe(el));

    return () => observer.disconnect();
  }, []);

  return (
    
{images.map(img => ( React中使用懒加载图片时,为什么部分图片在滚动时会重复加载? ))}
); }

控制台没报错,但用性能面板看请求记录,那些图片的src确实被多次发送请求…

我来解答 赞 8 收藏
二维码
手机扫码查看
2 条解答
欧阳小青
问题出在你没给每个图片唯一的key值,React在重渲染时会混淆这些图片。加上唯一key试试:

{images.map((img, index) => (
key={index}
id={img.id}
className="lazy-img"
data-src={img.src}
alt={img.alt}
style={{ background: 'lightgray', width: '200px', height: '200px' }}
/>
))}


不过用index当key不是最佳实践,建议给图片加个唯一id。另外检查一下是不是有些图片的data-src重复了,这也可能导致问题。
点赞 8
2026-02-02 17:00
百里东慧
问题出在你的懒加载逻辑里,主要是 IntersectionObserver 的行为和你在滚动时的处理方式之间有些小冲突。原理是这样:当图片离开可视区域后,IntersectionObserver 并不会自动停止观察它,除非你明确调用 observer.unobserve(img)。但你在代码中只在 entry.isIntersectingtrue 时才调用了 unobserve,而如果图片滚动回可视区或者有其他情况导致重新进入视口,它又会被重新观察到,从而触发了重复加载。

下面是具体的解决方案和优化步骤:

---

### 1. 确保图片只加载一次
你需要记录哪些图片已经加载过,并且确保它们即使再次进入视口也不会重新触发加载逻辑。你已经在状态中存储了加载过的图片信息(loaded),但没有在 IntersectionObserver 的回调中检查这个状态。所以需要修改逻辑,在触发加载之前先检查是否已经加载过。

---

### 2. 修改代码实现
以下是改进后的代码:

function ImageList() {
const [loaded, setLoaded] = React.useState({}); // 记录已加载的图片

React.useEffect(() => {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
const img = entry.target;

// 检查是否已经加载过
if (entry.isIntersecting && !loaded[img.id]) {
img.src = img.dataset.src; // 设置真实图片路径
observer.unobserve(img); // 停止对该元素的观察
setLoaded((prev) => ({ ...prev, [img.id]: true })); // 更新加载状态
}
});
}, { rootMargin: '0px', threshold: 0.1 });

// 初始化观察器
document.querySelectorAll('.lazy-img').forEach((el) => observer.observe(el));

return () => observer.disconnect(); // 清理观察器
}, [loaded]); // 将 loaded 加入依赖数组

return (
<div>
{images.map((img, index) => (
<img
key={index}
id={img-${index}}
className="lazy-img"
data-src={img.url} // 使用 data-src 存储真实图片路径
src="" // 初始为空
alt={img.alt}
/>
))}
</div>
);
}


---

### 3. 解释改动的地方
- **加入对 loaded 的检查**:在 if (entry.isIntersecting) 后面增加了一个条件 !loaded[img.id],确保只有未加载过的图片才会触发加载逻辑。
- **将 loaded 加入依赖数组**:这是因为我们在 useEffect 中使用了 loaded,React 需要知道它的变化来重新运行效果函数。
- **清理观察器**:通过 observer.disconnect() 来确保组件卸载时不会留下无用的观察器。

---

### 4. 其他可能的问题点
如果你还是发现有重复加载的情况,可以考虑以下几点:
- **浏览器缓存策略**:有时候浏览器会重新发起请求,但这并不意味着图片被重新下载。可以通过开发者工具中的网络面板查看响应头,确认是否有 Cache-ControlETag
- **图片 ID 的唯一性**:确保每个图片的 id 是唯一的,否则可能会出现状态混乱。

---

### 5. 测试建议
改完代码后,记得在不同设备和浏览器下测试一下懒加载的效果。尤其是移动端,滚动行为可能会更复杂。

希望这些改动能帮你解决问题!如果有其他疑问随时问哈~
点赞 1
2026-01-30 13:14