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

RTK Query实现无限滚动如何合并新旧数据且保留数据失效机制

RTK Query 无限滚动场景新旧数据合并官方实现方案

RTK Query 原生支持无限滚动/分页加载场景的新旧数据合并需求,禁止在组件层手动拼接新旧数据,这种做法脱离缓存层管理,确实会导致缓存失效、自动更新能力失效,官方推荐通过 endpoint 内置的三个配置项在缓存层完成合并,完整保留所有原生能力。


核心实现配置

在定义对应 API endpoint 时,添加以下三个配置项即可:

getPosts: builder.query({
  // 基础请求逻辑,首次拉取传page=1,后续滚动到底部传入累加的page值
  query: (page = 1) => `/posts?page=${page}&pageSize=5`,

  // 1. 序列化缓存key:让所有分页请求归属到同一个缓存条目下
  // 如果列表有筛选、排序参数,需要把这部分参数加入key,避免不同筛选条件的列表数据混淆
  serializeQueryArgs: ({ endpointName, queryArgs }) => {
    return `${endpointName}-${queryArgs.sortBy ?? 'default'}-${queryArgs.tag ?? 'all'}`
  },

  // 2. 缓存层合并逻辑:这是合并新旧数据的核心位置,运行在RTKQ内部缓存层,不会破坏自动更新能力
  merge: (currentCache, responseData) => {
    // 先做ID去重,避免快速滚动重复请求导致列表出现重复项
    const existingPostIds = new Set(currentCache.list.map(post => post.id))
    const newUniquePosts = responseData.list.filter(post => !existingPostIds.has(post.id))
    
    // 追加新数据到现有缓存
    currentCache.list.push(...newUniquePosts)
    // 更新分页元信息,比如是否还有下一页、当前最新页码
    currentCache.hasMore = responseData.hasMore
    currentCache.latestPage = responseData.currentPage
  },

  // 3. 强制重拉判断:避免相同参数重复请求,同时保证新页码请求正常发起
  forceRefetch: ({ currentArg, previousArg }) => {
    return currentArg !== previousArg
  },

  // 正常配置标签,用于后续缓存失效
  providesTags: (result) => {
    return [
      { type: 'Post', id: 'LIST' },
      ...(result?.list ?? []).map(post => ({ type: 'Post', id: post.id }))
    ]
  }
})

组件层使用方式

组件层不需要做任何数据拼接,正常维护页码状态、调用hook即可,返回的data已经是合并完成的全量列表数据:

import { useState } from 'react'
import { useGetPostsQuery } from './api/posts'

export default function PostList() {
  const [page, setPage] = useState(1)
  // 直接取合并后的数据,所有缓存更新、自动刷新能力完全正常
  const { data, isFetching } = useGetPostsQuery(page)

  const handleScroll = (e) => {
    const { scrollTop, clientHeight, scrollHeight } = e.target
    // 滚动到底部且还有更多数据、不在加载中时,页码+1触发下一页拉取
    if (
      scrollTop + clientHeight >= scrollHeight - 100 &&
      !isFetching &&
      data?.hasMore
    ) {
      setPage(prev => prev + 1)
    }
  }

  return (
    <div onScroll={handleScroll} style={{ height: '80vh', overflow: 'auto' }}>
      {data?.list.map(post => (
        <div key={post.id} className="post-item">
          <h3>{post.title}</h3>
          <p>{post.content}</p>
        </div>
      ))}
      {isFetching && <div>加载中...</div>}
    </div>
  )
}

方案优势

  • 完全保留RTK Query原生能力:单条Post数据变更、主动调用invalidateTags失效缓存时,合并后的列表会自动同步更新,不会出现本地拼接数据不刷新的问题
  • 自动避免重复请求:相同页码的请求不会重复发起
  • 缓存独立:不同筛选、排序条件的列表会生成独立缓存,不会互相污染
  • 不需要在组件层维护额外的列表状态,逻辑更简洁

注意事项

  • merge函数内请直接在传入的currentCache对象上做追加修改,不要整体替换currentCache的引用,否则会破坏响应式追踪
  • 必须加ID去重逻辑,防止快速滚动触发重复请求导致列表重复
  • 如果需要重置列表(比如切换筛选条件、手动刷新),直接重置page状态为1即可,RTK Query会自动清空对应缓存重新拉取首屏数据

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 05:01:02