一、@tanstack/react-query-devtools
是干什么的?
这是 React Query 的调试工具面板,可以:
- 查看当前缓存中有哪些 query
- 查看每个 query 的状态(loading、error、stale 等)
- 手动触发刷新、失效
- 查看 queryKey、数据内容、错误信息等
1. 安装
npm install @tanstack/react-query-devtools
2. 使用方法(和 Provider 一起加)
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
const queryClient = new QueryClient()
function App() {
return (
<QueryClientProvider client={queryClient}>
<YourApp />
<ReactQueryDevtools initialIsOpen={false} /> {/* 开关面板 */}
</QueryClientProvider>
)
}
添加后,开发环境中右下角会出现一个 React Query 图标,点开即可查看所有 query 状态。
二、分页加载 vs 无限滚动
我们这里演示一个 分页加载(点击按钮翻页) 和 无限滚动(滚动到底加载更多) 的示例。
示例场景:
假设有一个 API 支持分页:
GET /api/posts?page=1
返回:
{
data: [{ id, title }...],
nextPage: 2,
hasNextPage: true
}
三、分页加载示例(点击“加载更多”)
import { useInfiniteQuery } from '@tanstack/react-query'
const fetchPosts = async ({ pageParam = 1 }) => {
const res = await fetch(`/api/posts?page=${pageParam}`);
if (!res.ok) throw new Error('获取失败');
return res.json(); // 返回 { data, nextPage, hasNextPage }
};
function PostList() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isLoading,
isError,
error
} = useInfiniteQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
getNextPageParam: (lastPage, allPages) => {
return lastPage.hasNextPage ? lastPage.nextPage : undefined;
}
});
if (isLoading) return <p>加载中...</p>;
if (isError) return <p>错误: {error.message}</p>;
return (
<div>
{data.pages.map((page, i) => (
<React.Fragment key={i}>
{page.data.map(post => (
<div key={post.id}>{post.title}</div>
))}
</React.Fragment>
))}
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage ? '加载中...' : hasNextPage ? '加载更多' : '没有更多了'}
</button>
</div>
);
}
四、无限滚动(IntersectionObserver 方式)
除了按钮点击,我们也可以让它“滑到底部自动加载下一页”:
import { useRef, useEffect } from 'react';
function PostList() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isLoading,
isError,
error
} = useInfiniteQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
getNextPageParam: (lastPage) =>
lastPage.hasNextPage ? lastPage.nextPage : undefined,
});
const loader = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(entries => {
if (entries[0].isIntersecting && hasNextPage) {
fetchNextPage();
}
});
if (loader.current) {
observer.observe(loader.current);
}
return () => {
if (loader.current) observer.unobserve(loader.current);
};
}, [fetchNextPage, hasNextPage]);
if (isLoading) return <p>加载中...</p>;
if (isError) return <p>错误: {error.message}</p>;
return (
<div>
{data.pages.map((page, i) => (
<React.Fragment key={i}>
{page.data.map(post => (
<div key={post.id}>{post.title}</div>
))}
</React.Fragment>
))}
<div ref={loader}>
{isFetchingNextPage ? '加载中...' : hasNextPage ? '继续滑动加载更多' : '到底了'}
</div>
</div>
);
}
五、注意事项
- 所有分页逻辑靠
getNextPageParam
决定是否加载下一页 pages
是二维数组:data.pages[n].data[]
- 服务端必须支持分页传参与返回
- 如果用的是 Next.js,API 路由
/api/posts?page=x
可以自己 mock 一下数据