Apollo Client调用fetchMore后如何正确实现数据重拉取
Apollo Client 分页场景「缓存优先+后台静默更新」最佳实践
offset分页场景下,默认配置出现「refetch拉取位置错误」「多页加载结果被覆盖」是非常典型的问题,不需要手写复杂的useEffect refetch逻辑,按标准方案配置即可实现「进页先展示缓存、后台静默校验更新、上拉加载不丢数据」的需求。
第一步:配置查询策略,实现「先渲染缓存、后台校验更新」
直接给useQuery配置fetchPolicy即可匹配目标交互,不需要手动在useEffect里调用refetch:
const PAGE_SIZE = 10; // 移除自行维护的page状态,避免状态和缓存数据不同步 const { data, loading, fetchMore } = useQuery(MY_QUERY, { variables: { limit: PAGE_SIZE, offset: 0 }, // 首次查询:立刻返回缓存内容渲染,同时后台发请求拉最新数据,拿到结果自动更新UI fetchPolicy: 'cache-and-network', // 后续组件重挂载/再次触发查询:优先用缓存,不强制每次都发请求 nextFetchPolicy: 'cache-first', });
cache-and-network策略完全匹配需求:用户进页面不会白屏等请求,先看到之前缓存的内容,后台静默拉取最新数据,有更新就自动刷新UI,无更新则用户无感知。
第二步:配置缓存合并规则,解决多页数据丢失问题
之前调用refetch({offset:0})丢失已加载的多页数据,核心原因是Apollo默认的缓存逻辑会用新返回的结果直接覆盖同字段缓存,只要给分页列表字段配置自定义合并逻辑,就能同时支持「上拉加载更多拼接到尾部」「拉取最新首页拼接到头部去重」两个场景:
import { ApolloClient, InMemoryCache } from '@apollo/client'; const client = new ApolloClient({ uri: '你的接口地址', cache: new InMemoryCache({ typePolicies: { Query: { fields: { // 替换成你查询里实际的列表字段名,比如posts、goods、searchResult这类 yourListFieldName: { // 关闭按变量拆分缓存分页结果,所有分页数据存在同一个缓存条目下 keyArgs: false, merge(existing = [], incoming, { args }) { // 拉取offset=0的最新首页数据时,和已有缓存做去重合并,不覆盖旧数据 if (args?.offset === 0) { const merged = [...incoming]; // 遍历已缓存的历史数据,按唯一缓存引用去重,避免重复展示 existing.forEach(cachedItem => { const isDuplicate = merged.some( newItem => newItem.__ref === cachedItem.__ref ); if (!isDuplicate) merged.push(cachedItem); }); return merged; } // 上拉加载更多时,把新页数据拼到现有列表尾部 return [...existing, ...incoming]; }, }, }, }, }, }), });
第三步:修正上拉加载更多逻辑
之前自己维护page状态很容易和实际缓存数据长度不一致(比如后台刷新插入了新数据),直接用当前已渲染的列表长度计算offset即可:
const handleFetchMore = () => { const currentListLength = data?.yourListFieldName?.length || 0; fetchMore({ variables: { limit: PAGE_SIZE, offset: currentListLength, }, }); };
原有两种方案的问题说明
- 无参数调用
refetch():Apollo会复用该查询最后一次请求的variables,如果你之前已经翻到第3页,最后一次请求的offset是30,refetch就会拉30-40条的数据,自然拿不到列表头部的新内容。 - 传
offset:0调用refetch():因为没有配置上述merge规则,Apollo会把返回的前10条数据直接覆盖缓存里所有已加载的几十条数据,导致之前加载的多页内容丢失。
如果需要控制后台刷新的频率,只需要在merge逻辑里增加时间戳判断,距离上次拉取小于指定阈值(比如5分钟)就跳过网络请求即可,不需要额外引入其他依赖。
内容的提问来源于stack exchange,提问作者Evanss
相关产品推荐
相关产品推荐

