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

Next.js中useSWRInfinite切换查询参数时的无限滚动异常问题

解决Next.js中useSWRInfinite切换筛选条件后无限滚动加载旧数据的问题

问题根源分析

你的代码存在几个核心问题,导致切换genre后仍加载旧数据:

  • getKey函数未正确关联当前选中的genreFilter(代码里误用了未定义的genres变量),且未处理分页逻辑的终止条件
  • 不同genre的缓存未隔离,SWR仍会复用旧genre的分页缓存
  • 切换genre时的重置方式不正确,未彻底触发新缓存的加载

修正后的完整代码

import { useRouter, useSearchParams } from 'next/navigation';
import useSWRInfinite from 'swr/infinite';

const API_ENDPOINT = "https://example.com/api/movies";
// 假设GENRES是你的genre列表
const GENRES = ["Action", "Comedy", "Drama"];

export function Scroller() {
  const router = useRouter();
  const searchParams = useSearchParams();
  const genreFilter = searchParams.get("genres") || "Action"; // 设置默认genre为Action

  // 修正getKey:关联当前genre,处理分页终止条件
  const getKey = (pageIndex, previousPageData) => {
    // 如果上一页没有数据,说明已到末尾,停止生成新请求的key
    if (previousPageData?.movies.length === 0) return null;
    
    const page = pageIndex + 1; // pageIndex从0开始,接口页码通常从1开始
    // 生成包含genre的唯一key,确保不同genre的缓存完全隔离
    const params = new URLSearchParams({ genres: genreFilter, page });
    return `${API_ENDPOINT}?${params}`;
  };

  // 修正fetcher:处理响应解析和错误
  const fetcher = async (url) => {
    const res = await fetch(url);
    if (!res.ok) throw new Error(`请求失败:${res.status}`);
    return res.json();
  };

  // 初始化useSWRInfinite,当genreFilter变化时,getKey生成的键会更新,自动触发新缓存加载
  const { data, size, setSize, isLoading } = useSWRInfinite(getKey, fetcher);

  // 合并所有页的电影数据
  const allMovies = data ? data.flatMap(page => page.movies) : [];

  return (
    <div>
      {/* Genre切换按钮 */}
      <div>
        {GENRES.map((g) => (
          <button
            key={g}
            onClick={() => {
              // 更新路由参数
              router.push(`/?genres=${g}`);
              // 重置加载页数为1,强制从新genre的第一页开始加载
              setSize(1);
            }}
            style={{ 
              margin: '0 8px',
              padding: '4px 12px',
              backgroundColor: genreFilter === g ? '#2196f3' : '#e0e0e0',
              color: genreFilter === g ? 'white' : 'black',
              border: 'none',
              borderRadius: '4px',
              cursor: 'pointer'
            }}
          >
            {g}
          </button>
        ))}
      </div>

      {/* 电影列表 */}
      <div style={{ marginTop: '24px' }}>
        {allMovies.map((movie) => (
          <div key={movie.id} style={{ padding: '12px', borderBottom: '1px solid #eee' }}>
            {movie.name}
          </div>
        ))}

        {/* 加载状态提示 */}
        {isLoading && <div style={{ padding: '24px', textAlign: 'center' }}>加载中...</div>}

        {/* 无限滚动触发逻辑(示例:滚动到底部加载) */}
        <div 
          style={{ height: '40px' }}
          onIntersection={([entry]) => {
            // 仅当未加载中、且还有更多数据时,加载下一页
            if (!isLoading && entry.isIntersecting && data?.[data.length - 1]?.movies.length > 0) {
              setSize(prevSize => prevSize + 1);
            }
          }}
        />
      </div>
    </div>
  );
}

关键修复点说明

  1. 缓存隔离:getKey生成的URL包含当前genreFilter,确保不同genre的分页缓存完全独立,切换genre后SWR会自动使用新的缓存组,不会复用旧数据
  2. 分页逻辑修正:添加previousPageData判断,当某一页没有数据时停止生成新请求,避免无效调用
  3. 正确的重置方式:切换genre时调用setSize(1),强制将加载页数重置为1,触发新genre的第一页加载,同时配合路由参数更新,保证组件状态与URL同步
  4. 数据合并:使用flatMap将多页数据合并为一个数组,统一渲染逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 16:58:12