You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Apollo Client v3偏移量分页:缓存未命中重复请求排查

Apollo Client 偏移量分页缓存不命中问题

我按照Apollo Client文档实现了基于偏移量的分页功能:

  • 从第1页切到第2页、第2页切到第3页时,正常向GraphQL服务器发起查询
  • 但从第3页返回第2页、第2页返回第1页时,依然触发查询请求,说明没读取缓存(缓存已按预期存为单个列表)

请问问题出在哪?有没有人完全按文档实现过分页功能?


组件中useQuery调用

const {loading:loadingHistory, data:historyData, fetchMore} = useQuery(getHistoryOffsetPaged, {
  variables: {
    offset: 0,
    limit: store.selectedNumberOfElements
  }
})

分页按钮逻辑

<PageButtons
  totalPages={store.historyTotalPages}
  currentPage={store.historyCurrentPage}
  changePage={page => {
    fetchMore({
      variables: {
        offset: (page - 1) * store.selectedNumberOfElements,
        limit: store.selectedNumberOfElements
      }
    })
    store.setHistoryCurrentPage(page)
   }}
/>

GraphQL查询定义

export const getHistoryOffsetPaged = gql`
    query(
        $offset: Int,
        $limit: Int) {
        getHistoryOffsetPaged(
            offset: $offset,
            limit: $limit) {
                id
                dateTime
                curFrom
                curTo
                exRate
                sumBeforeConversion
                sumAfterConversion
                averageExRate
            }
        }
`

缓存配置

cache: new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        getHistoryOffsetPaged: {
          keyArgs: false,
          merge(existing, incoming, {
            args: {
              offset = 0
            }
          }) {
            const merged = existing ? existing.slice(0) : [];
            for (let i = 0; i < incoming.length; ++i) {
              merged[offset + i] = incoming[i];
            }
            return merged;
          },
          read(existing, { args }) {
            return existing && existing.slice(
                args?.offset,
                args?.offset + args?.limit);
          }
        }
      }
    }
  }
})

问题原因分析

核心问题是用错了分页触发方式:

  • fetchMore的设计目的是追加加载数据(比如滚动加载),不是切换页面。它不会更新useQuery的核心查询变量,只是把新数据合并到缓存列表中
  • 你初始的useQuery始终用offset:0的变量,切换页面时调用fetchMore传入新offset,但Apollo会为每个不同的变量组合生成独立的缓存查询条目。返回旧页面时,相当于用新的变量组合发起查询,而该组合的缓存条目不存在,因此触发网络请求

修正方案

直接通过更新useQuery的变量来切换页面,让Apollo自动利用缓存:

  1. 调整useQuery的变量,关联当前页码和每页条数:
const {loading:loadingHistory, data:historyData} = useQuery(getHistoryOffsetPaged, {
  variables: {
    offset: (store.historyCurrentPage - 1) * store.selectedNumberOfElements,
    limit: store.selectedNumberOfElements
  },
  // 可选:根据需求设置fetchPolicy,默认是cache-first
  fetchPolicy: 'cache-and-network'
})
  1. 简化分页按钮逻辑,只需更新当前页码:
<PageButtons
  totalPages={store.historyTotalPages}
  currentPage={store.historyCurrentPage}
  changePage={page => {
    store.setHistoryCurrentPage(page)
   }}
/>
  1. 保持现有typePolicies配置不变,它会继续负责合并不同offset的查询结果到统一缓存列表,read函数会根据当前变量返回对应的数据切片。

补充说明

当你切换页面时,useQuery会自动根据新的offset/limit变量,先调用read函数从合并后的缓存列表中提取对应切片,如果数据存在就直接返回缓存,不会发起网络请求。只有当缓存中没有对应数据时,才会触发查询。

内容的提问来源于stack exchange,提问作者Alex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 01:46:47