为何绑定div元素后useRef对象仍为null?(无限滚动场景)
问题分析与解决方案
核心问题
- 同一ref绑定多个元素:你将
refetchTrigger同时绑定到renderDiv返回的div和循环渲染的每个TrackGridItem外层div,导致ref的current值被反复覆盖,无法稳定指向目标触发元素。 - IntersectionObserver监听失效:
useIntersection的useEffect依赖为空数组,仅在组件初始化时执行一次,此时ref尚未关联DOM元素,后续元素挂载后也不会重新创建监听。 - 状态更新闭包问题:更新
list时直接使用[...list, ...tracks],闭包会捕获旧的list值,可能导致数据累加错误。 - 无效日志时机:
renderDiv内的日志在JSX创建阶段执行,此时元素还未挂载到DOM,ref的current必然是null,该日志无实际参考意义。
修复后的代码
1. 修正TrackGrid组件
export const TrackGrid = () => { const [list, setList] = useState<Track[]>([]); const [page, setPage] = useState<number>(1); const refetchTrigger = useRef<HTMLDivElement | null>(null); const inViewport = useIntersection(refetchTrigger, "0px"); const { tracks, error, isValidating } = useGetTracks(false, page, 20); useEffect(() => { // 加入isValidating判断,避免重复发起请求 if (inViewport && !isValidating) { setPage(prev => prev + 1); } }, [inViewport, isValidating]); useEffect(() => { if (tracks) { // 用函数式更新获取最新状态,避免闭包问题 setList(prevList => [...prevList, ...tracks]); } }, [tracks]); return ( <> <div className="grid place-items-center grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4"> {list.map((track: Track, i: number) => ( <div key={i}> <TrackGridItem track={track} /> </div> ))} </div> {/* 将触发加载的div单独放在列表末尾,仅绑定一次ref */} {list.length > 0 && <div ref={refetchTrigger}>加载中...</div>} </> ); };
2. 修正useIntersection钩子
export const useIntersection = (element: React.RefObject<HTMLDivElement>, rootMargin: string) => { const [isVisible, setState] = useState<boolean>(false); useEffect(() => { const observer = new IntersectionObserver( ([entry]) => { setState(entry.isIntersecting); }, { rootMargin } ); const currentElement = element.current; if (currentElement) { observer.observe(currentElement); } return () => { if (currentElement) { observer.unobserve(currentElement); } }; }, [element, rootMargin]); // 添加依赖,当ref或rootMargin变化时重新监听 return isVisible; };
关键修复点说明
- 单一ref绑定:仅将
refetchTrigger绑定到列表末尾的加载触发div,确保ref始终指向唯一目标元素。 - 更新监听依赖:为
useIntersection的useEffect添加element和rootMargin依赖,当ref关联的DOM元素变化时自动重建监听。 - 函数式状态更新:更新
list和page时使用函数式更新,避免闭包导致的旧状态引用问题。 - 防重复请求:通过
isValidating状态判断,避免同一时间发起多个重复的分页请求。
内容的提问来源于stack exchange,提问作者Richard Ryan Garcia
相关产品推荐
相关产品推荐

