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

Preact函数式组件:异步加载列表后如何用IntersectionObserver监听元素

解决Preact动态列表项的IntersectionObserver监听问题

核心思路

因为你的列表项是异步请求后才渲染的,必须等数据加载完成、DOM渲染完毕后,再给列表项添加监听。结合Preact的hooks,用useRef保存Observer实例,用useEffect监听数据变化触发监听逻辑,就能解决这个问题。

具体实现代码

方式一:基于选择器批量监听

import { useEffect, useRef, useState } from 'preact/hooks';

function MyListComponent() {
  // 用ref存Observer,避免每次渲染重建
  const observerRef = useRef<IntersectionObserver | null>(null);
  // 存储服务器返回的列表数据
  const [listData, setListData] = useState([]);

  // 初始化IntersectionObserver,组件挂载时只执行一次
  useEffect(() => {
    const intersectionCallback = (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          console.log('IN OBSERVER', entry.target);
          // 这里写你要发起的额外请求逻辑
          observer.unobserve(entry.target); // 完成后停止监听当前项
        }
      });
    };

    const options = {
      root: document.getElementById('my-list-element-id'), 
      rootMargin: '0px',                              
      threshold: 0,                                   
    };

    observerRef.current = new IntersectionObserver(intersectionCallback, options);

    // 组件卸载时销毁Observer,防止内存泄漏
    return () => observerRef.current?.disconnect();
  }, []);

  // 从服务器获取列表数据
  useEffect(() => {
    async function fetchList() {
      const res = await fetch('/api/your-list-api');
      const data = await res.json();
      setListData(data);
    }
    fetchList();
  }, []);

  // 数据更新后,给渲染好的列表项加监听
  useEffect(() => {
    if (!observerRef.current || listData.length === 0) return;

    // 用requestAnimationFrame确保DOM已渲染完成
    requestAnimationFrame(() => {
      const rows = document.querySelectorAll('list-view-row-element');
      rows.forEach(row => observerRef.current?.observe(row));
    });

    // 数据更新前,先取消旧列表项的监听,避免重复监听
    return () => {
      const rows = document.querySelectorAll('list-view-row-element');
      rows.forEach(row => observerRef.current?.unobserve(row));
    };
  }, [listData]);

  // 渲染列表行的函数
  const renderRows = () => {
    return listData.map(item => (
      <list-view-row-element key={item.id}>
        {/* 这里是你的列表项内容 */}
      </list-view-row-element>
    ));
  };

  return (
    <div id="my-list-element-id">
      {renderRows()}
    </div>
  );
}

方式二:给每个列表项单独加ref(更精准)

如果不想依赖选择器,或者列表项结构复杂,直接给每个行存ref,渲染时直接监听:

import { useEffect, useRef, useState } from 'preact/hooks';

function MyListComponent() {
  const observerRef = useRef<IntersectionObserver | null>(null);
  const [listData, setListData] = useState([]);
  // 存储每个列表项的DOM引用
  const rowRefs = useRef<(Element | null)[]>([]);

  // 初始化Observer同上...

  // 渲染列表时保存每个行的ref
  const renderRows = () => {
    return listData.map((item, index) => (
      <list-view-row-element 
        key={item.id}
        ref={el => rowRefs.current[index] = el}
      >
        {/* 列表项内容 */}
      </list-view-row-element>
    ));
  };

  // 数据更新后,遍历ref添加监听
  useEffect(() => {
    if (!observerRef.current) return;

    rowRefs.current.forEach(el => {
      if (el) observerRef.current?.observe(el);
    });

    // 清理旧监听
    return () => {
      rowRefs.current.forEach(el => {
        if (el) observerRef.current?.unobserve(el);
      });
    };
  }, [listData]);

  // 数据请求逻辑同上...

  return (
    <div id="my-list-element-id">
      {renderRows()}
    </div>
  );
}

关键注意点

  • 用useRef存Observer:保证整个组件生命周期只有一个Observer实例,避免重复创建浪费资源。
  • 依赖listData的useEffect:数据更新后才触发监听,此时列表项已经渲染到DOM里,能正确找到元素。
  • requestAnimationFrame:确保DOM渲染完成再执行选择器查询,避免找不到元素的问题。
  • 清理函数:每次数据更新前取消旧监听,防止同一个元素被重复监听;组件卸载时销毁Observer,避免内存泄漏。

内容的提问来源于stack exchange,提问作者se22as

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 20:23:21