API无内置分页机制,如何用Apollo/GraphQL高阶组件实现分页?
嘿,好问题!虽然你当前使用的API没指定分页机制,但Apollo Client确实提供了几种开箱即用的分页方案,能帮你优雅处理大量书籍数据的加载。下面我就给你拆解两种最常用的实现方式,以及怎么适配你的现有代码:
常见的Apollo分页方案
1. 偏移分页(Offset-Based Pagination)
这是最直观的分页方式,核心是用offset(起始位置)和limit(每页数量)两个参数来控制返回的数据范围。
首先你需要修改GraphQL查询,新增这两个参数:
export const books = gql` query Query($offset: Int!, $limit: Int!) { books(offset: $offset, limit: $limit) { title } } `;
接下来在组件里管理分页状态——如果是函数组件,我们可以用useState配合Apollo的useQuery钩子实现:
import { useQuery } from '@apollo/client'; import { useState } from 'react'; import { books } from './your-query-path'; function BookList() { // 初始化偏移量为0,每页显示10本书 const [offset, setOffset] = useState(0); const limit = 10; const { loading, error, data } = useQuery(books, { variables: { offset, limit }, }); if (loading) return <p>加载中...</p>; if (error) return <p>出错了:{error.message}</p>; return ( <div> <ul> {data.books.map((book, idx) => ( <li key={idx}>{book.title}</li> ))} </ul> {/* 分页按钮 */} <div> {offset > 0 && ( <button onClick={() => setOffset(prev => prev - limit)}>上一页</button> )} <button onClick={() => setOffset(prev => prev + limit)}>下一页</button> </div> </div> ); } export default BookList;
如果你还是想用原来的高阶组件(graphql HOC)方式,也可以在组件内部维护offset和limit的state,再通过变量传递给查询。
不过要注意:偏移分页在数据频繁插入/删除时,可能会出现重复或遗漏数据的问题(比如在你翻页期间,有新书籍插入到前面,下一页就可能重复加载之前的内容)。如果你的书籍数据经常变动,更推荐下面的游标分页。
2. 游标分页(Cursor-Based Pagination)
这是GraphQL官方推荐的分页方案,用一个唯一的“游标”(比如书籍的ID、创建时间戳)标记当前分页的位置,能避免偏移分页的弊端。
首先修改查询,新增after(游标标记)和first(每页数量)参数,同时让API返回分页元数据(pageInfo)和包含游标与书籍数据的edges:
export const books = gql` query Query($after: String, $first: Int!) { books(after: $after, first: $first) { edges { node { title } cursor } pageInfo { hasNextPage # 是否还有下一页 endCursor # 当前页最后一条数据的游标 } } } `;
然后在组件里用Apollo的fetchMore方法加载下一页,并合并数据:
import { useQuery } from '@apollo/client'; import { useState } from 'react'; import { books } from './your-query-path'; function BookList() { const [cursor, setCursor] = useState(null); const first = 10; const { loading, error, data, fetchMore } = useQuery(books, { variables: { after: cursor, first }, }); if (loading) return <p>加载中...</p>; if (error) return <p>出错了:{error.message}</p>; const handleLoadMore = () => { fetchMore({ variables: { after: data.books.pageInfo.endCursor }, // 合并新旧数据 updateQuery: (prevResult, { fetchMoreResult }) => { if (!fetchMoreResult) return prevResult; return { books: { ...fetchMoreResult.books, edges: [...prevResult.books.edges, ...fetchMoreResult.books.edges], }, }; }, }); }; return ( <div> <ul> {data.books.edges.map(({ node }, idx) => ( <li key={idx}>{node.title}</li> ))} </ul> {/* 只有还有下一页时显示按钮 */} {data.books.pageInfo.hasNextPage && ( <button onClick={handleLoadMore}>加载更多</button> )} </div> ); } export default BookList;
关键注意点
- 不管用哪种分页方式,后端API必须支持对应的参数(比如
offset/limit或者after/first)。如果后端现在不支持,你需要先修改后端的GraphQL Schema和Resolver,添加这些参数的处理逻辑。 - 如果后端暂时没法修改,你也可以在前端做“假分页”:先一次性获取所有书籍,再用前端state控制显示范围。但这种方式只适合数据量小的场景,数据量大的话会严重影响页面性能。
内容的提问来源于stack exchange,提问作者Ilja
相关产品推荐
相关产品推荐

