如何结合useInfinityQuery与Directus实现无限滚动?
在Directus集合上用TanStack Query实现无限滚动的实操方案
前置准备
确保你已经安装了@tanstack/react-query和Directus官方SDK(@directus/sdk),并且已配置好Directus客户端实例。
核心实现逻辑
用TanStack Query的useInfiniteQuery钩子,结合Directus的limit(每页条数)和offset(偏移量)分页参数分批拉取数据。每次触发下一页请求时,把offset设为上一页数据的总条数,以此实现无限滚动。
完整组件示例
import { useInfiniteQuery } from '@tanstack/react-query'; import { createDirectus, rest, readItems } from '@directus/sdk'; import { useEffect, useRef, useState } from 'react'; // 初始化Directus客户端 const directus = createDirectus('你的Directus实例地址').with(rest()); // 封装拉取集合数据的函数 const fetchCollectionItems = async ({ pageParam = 0 }) => { const pageSize = 10; // 每页加载10条,可按需调整 const response = await directus.request( readItems('你的集合名称', { limit: pageSize, offset: pageParam, // 可选:添加排序、过滤、指定返回字段等配置 sort: ['-created_at'], fields: ['id', 'title', 'description', 'cover_image'] }) ); return { items: response, nextOffset: pageParam + pageSize, hasMore: response.length === pageSize // 返回条数等于每页大小,说明还有更多数据 }; }; export const InfiniteScrollCardList = () => { const { data, fetchNextPage, hasNextPage, isLoading, isError, error } = useInfiniteQuery({ queryKey: ['collection-items'], queryFn: fetchCollectionItems, getNextPageParam: (lastPage) => { // 有更多数据就返回下一页偏移量,否则返回undefined终止分页 return lastPage.hasMore ? lastPage.nextOffset : undefined; } }); const loadMoreTrigger = useRef(null); const [isFetchingNext, setIsFetchingNext] = useState(false); // 用Intersection Observer监听加载触发元素 useEffect(() => { const observer = new IntersectionObserver( (entries) => { const entry = entries[0]; if (entry.isIntersecting && hasNextPage && !isFetchingNext) { setIsFetchingNext(true); fetchNextPage().finally(() => setIsFetchingNext(false)); } }, { threshold: 0.1 } ); if (loadMoreTrigger.current) observer.observe(loadMoreTrigger.current); return () => { if (loadMoreTrigger.current) observer.unobserve(loadMoreTrigger.current); }; }, [hasNextPage, fetchNextPage, isFetchingNext]); // 处理基础状态 if (isLoading) return <div>加载中...</div>; if (isError) return <div>加载失败:{(error as Error).message}</div>; return ( <div className="card-list"> {/* 渲染所有已加载的卡片 */} {data?.pages.map((page, pageIdx) => ( <React.Fragment key={pageIdx}> {page.items.map((item) => ( <div key={item.id} className="card"> <h3>{item.title}</h3> <p>{item.description}</p> {item.cover_image && ( <img src={`${directus.url}/assets/${item.cover_image}`} alt={item.title} /> )} </div> ))} </React.Fragment> ))} {/* 加载触发元素 */} {hasNextPage && ( <div ref={loadMoreTrigger} className="load-more"> {isFetchingNext ? '加载更多中...' : '滚动加载更多'} </div> )} {!hasNextPage && <div>已加载全部内容</div>} </div> ); };
关键细节说明
- Directus分页规则:
offset从0开始计数,每次请求的偏移量等于上一页偏移量+每页大小;当返回数据条数小于每页大小时,说明没有更多数据。 - 滚动监听优化:用Intersection Observer替代传统scroll事件,更高效且能精准检测元素进入视口的时机。
- TanStack Query优势:自动处理数据缓存、重复请求拦截、加载/错误状态管理,无需手动维护这些逻辑。
- 权限注意:确保你的Directus账号对目标集合有
read权限,否则会返回权限错误。
内容的提问来源于stack exchange,提问作者LLa
相关产品推荐
相关产品推荐

