基于偏移量的分页
我们建议阅读核心分页 API,然后再学习基于偏移量分页的特定考虑因素。
使用基于偏移量的分页,一个列表字段接受一个 参数,它表示服务器在返回特定查询的项目时应从列表中的哪里开始。该字段通常也接受一个 参数,表示要返回的最大项目数:offset
字段 表示服务器返回项目时应从列表中的哪个位置开始。该字段通常也接受一个 limit
参数,表示要返回的最大项目数:
type Query {feed(offset: Int, limit: Int): [FeedItem!]}type FeedItem {id: ID!message: String!}
这种分页策略适用于不可变列表,或者列表中每个项目的索引永远不会改变的情况。在其他情况下,应避免使用它,转而选择 基于游标的分页,因为移动或删除项目会改变偏移量。如果分页查询之间发生变化,这将导致跳过或重复项目。
尽管它有限制,但基于偏移量的分页在很多应用中是一个常见的模式,部分原因是因为它的实现相对简单。
offsetLimitPagination
辅助函数
Apollo 客户端提供了一个 offsetLimitPagination
辅助函数,您可以用来为每个相关的列表 字段 生成 字段策略
此示例使用 offsetLimitPagination
为 Query.feed
生成字段策略:
import { InMemoryCache } from "@apollo/client";import { offsetLimitPagination } from "@apollo/client/utilities";const cache = new InMemoryCache({typePolicies: {Query: {fields: {feed: offsetLimitPagination()},},},});
这定义了一个 merge
函数,用于处理缓存中分页结果的合并(查看源代码)。
与 fetchMore
结合使用
如果您使用 offsetLimitPagination
如上设置您的源策略,那么您可以像这样使用 fetchMore
和 useQuery
:
const FeedData() {const { loading, data, fetchMore } = useQuery(FEED_QUERY, {variables: {offset: 0,limit: 10},});// If you want your component to rerender with loading:true whenever// fetchMore is called, add notifyOnNetworkStatusChange:true to the// options you pass to useQuery above.if (loading) return <Loading/>;return (<Feedentries={data.feed || []}onLoadMore={() => fetchMore({variables: {offset: data.feed.length},})}/>);}
默认情况下,fetchMore
使用原始 查询 和 变量
,所以我们只需要传递变化的 变量: offset
。当从服务器返回新数据时,它会自动与缓存中任何现有的 Query.feed
数据合并。这导致 useQuery
使用扩展的数据列表重新渲染。
在本例中,Feed
组件每次渲染时都会接收到整个缓存的列表(data.feed
),这其中包括迄今为止接收到的所有页面的数据。这是一个 非分页的 read
函数。
使用分页 read
函数
在上述 示例中,GraphQL 服务器 返回单独页面的结果,但是每个 query 会返回迄今为止接收到的所有缓存结果。为了将每个 query 的结果限制为您请求的项目,您可以在您的 field 策略中包含一个 分页的 read
函数。
由于 offsetLimitPagination
助手当前定义您的 field 策略,您需要将您的 read
函数与助手的结果结合,如下所示:
import { InMemoryCache } from "@apollo/client";import { offsetLimitPagination } from "@apollo/client/utilities";const cache = new InMemoryCache({typePolicies: {Query: {fields: {feed: {...offsetLimitPagination(),read(existing, { args }) {// Implement here}}},},},});
有关示例实现,请参阅 分页 read
函数。
如果您使用分页的 read
函数,您可能需要在调用 fetchMore
后,根据您用例的需求更新您的 offset
和 limit
变量。否则,您将继续只渲染第一页的结果。
例如,要显示迄今为止接收到的所有数据,您可以对之前的示例进行如下修改
const FeedData = () => {const [limit, setLimit] = useState(10);const { loading, data, fetchMore } = useQuery(FEED_QUERY, {variables: {offset: 0,limit,},});if (loading) return <Loading/>;return (<Feedentries={data.feed || []}onLoadMore={() => {const currentLength = data.feed.length;fetchMore({variables: {offset: currentLength,limit: 10,},}).then(fetchMoreResult => {// Update variables.limit for the original query to include// the newly added feed items.setLimit(currentLength + fetchMoreResult.data.feed.length);});}}/>);}
此代码使用 React useState
Hook 存储当前的 limit
值,并通过在 fetchMore
返回的 Promise
的回调中调用 setLimit
来更新它。
如果需要改变 offset
,您也可以用 React useState
Hook 存储它。这些 变量
何时以及如何改变完全取决于您的组件,并且可能不总是调用 fetchMore
的结果,所以使用 React 组件状态来存储这些变量值是有意义的。
如果您不使用 React 和 useQuery
,则由 client.watchQuery
返回的 ObservableQuery
对象有一个名为 setVariables
的方法,可以调用它来更新原始 变量
。
因为 fetchMore
函数需要做一些额外工作来更新原始 变量(如果你使用的是对那些 read
函数敏感的 read
函数),所以说 fetchMore
鼓励使用第一种 read
函数,这个函数简单地返回所有可用数据。
然而,现在你了解了你的选项,将读取时的分页逻辑从你的应用代码移动到你的 field read
函数中没有什么错误。两种 read
函数都有其用途,并且都可以与 fetchMore
一起工作。
使用 offsetLimitPagination 设置 keyArgs
如果分页 field 接受除 offset 和 limit 之外的其他参数,你可能需要 指定 key arguments 来表示两个结果集是否属于同一列表或不同的列表。
要为 offsetLimitPagination
生成的 field 策略设置 keyArgs
,将参数名数组提供给函数作为参数:
fields {// Results belong to the same list only if both the type// and userId arguments match exactlyfeed: offsetLimitPagination(["type", "userId"])}
默认情况下, offsetLimitPagination
使用 keyArgs: false
(没有 key 参数)。