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

RTK Query无限滚动中invalidateTags失效问题求助

问题分析

当你通过invalidatesTags使列表相关的tag失效时,RTK Query会重新发起请求,但你的通用无限滚动函数中使用了本地维护的localOffset状态,导致重新请求时依然沿用当前的偏移量,而非从0开始加载全量数据,最终本地缓存的combinedData无法正确更新,出现数据不完整或旧数据残留的问题。

解决方案

核心思路是区分主动加载更多和被动缓存失效的重新请求,当检测到是缓存失效触发的被动请求时,自动重置本地状态到初始状态(offset=0,清空combinedData),确保重新获取全量数据。

修改后的useFetchQuery代码如下:

export const isValidNotEmptyArray = (array) =>
  !!(array && array?.length && array?.length > 0)

const useFetchQuery = (
  useGetDataListQuery,
  { offset = 0, limit = 10, departure, ...queryParameters },
  filter = () => true,
) => {
  const [localOffset, setLocalOffset] = useState(offset)
  const [combinedData, setCombinedData] = useState([])
  const [gotWiped, setGotWiped] = useState(0)
  const [isLoadingMore, setIsLoadingMore] = useState(false) // 标记是否为主动加载更多

  const queryResponse = useGetDataListQuery(
    {
      offset: localOffset,
      limit,
      ...queryParameters,
    },
  )
  const { data: fetchData = { result: [], total: 0 } } = queryResponse || {}
  const total = useMemo(() => fetchData.total, [fetchData])

  // 处理新数据的合并或重置
  useEffect(() => {
    const value = departure ? fetchData.result.departures : fetchData.result
    if (isValidNotEmptyArray(value)) {
      setGotWiped(0)
      if (localOffset === 0) {
        setCombinedData(value)
      } else {
        setCombinedData((previousData) => [...previousData, ...value])
      }
    } else if (gotWiped === 0) {
      setGotWiped(1)
    }
  }, [fetchData, localOffset, departure])

  // 清空数据逻辑
  useEffect(() => {
    if (gotWiped) {
      setCombinedData([])
    }
  }, [gotWiped])

  // 区分主动加载更多和被动缓存失效请求,被动请求时重置状态
  useEffect(() => {
    // 加载更多请求完成后重置标记
    if (!queryResponse.isFetching && isLoadingMore) {
      setIsLoadingMore(false)
    }
    // 被动重新请求(如tag失效)且当前offset非0时,重置到第一页
    if (queryResponse.isFetching && !isLoadingMore && localOffset !== 0) {
      setLocalOffset(0)
      setCombinedData([])
    }
  }, [queryResponse.isFetching, isLoadingMore, localOffset])

  const refresh = () => {
    setLocalOffset(0)
    setCombinedData([])
  }

  const loadMore = () => {
    if (combinedData.length < total) {
      setIsLoadingMore(true)
      setLocalOffset(combinedData.length)
    }
  }

  return {
    data: useMemo(() => combinedData.filter(filter), [combinedData, filter]),
    offset: localOffset,
    total: useMemo(() => combinedData.filter(filter).length, [combinedData, filter]),
    loadMore,
    refresh,
    isLoading: queryResponse?.isLoading,
    isFetching: queryResponse?.isFetching,
  }
}
关键修改点说明
  1. 添加isLoadingMore状态:标记当前请求是否是用户主动触发的加载更多,以此区分被动的缓存失效重新请求。
  2. 被动请求自动重置:当检测到非加载更多的后台请求(即tag失效触发的刷新),自动将localOffset重置为0并清空combinedData,确保重新获取全量第一页数据。
  3. 修正departure参数:将departure作为参数传入函数,避免依赖外部未声明变量导致的潜在bug。
  4. 简化refresh函数:直接将offset重置为0,无需设置为null。
  5. 优化total计算:前端过滤场景下,直接使用过滤后的数据长度作为total,逻辑更清晰。
额外建议
  • 确保invalidatesTags只针对列表的LIST tag(如{ type: 'Departures', id: 'LIST' }),避免误触单个item的tag导致不必要的请求。
  • 如果后端支持返回过滤后的总数,建议优先使用后端返回的total,减少前端计算开销。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 20:05:26