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
相关产品推荐
相关产品推荐

