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> ); }
关键修复点说明
- 缓存隔离:
getKey生成的URL包含当前genreFilter,确保不同genre的分页缓存完全独立,切换genre后SWR会自动使用新的缓存组,不会复用旧数据 - 分页逻辑修正:添加
previousPageData判断,当某一页没有数据时停止生成新请求,避免无效调用 - 正确的重置方式:切换genre时调用
setSize(1),强制将加载页数重置为1,触发新genre的第一页加载,同时配合路由参数更新,保证组件状态与URL同步 - 数据合并:使用
flatMap将多页数据合并为一个数组,统一渲染逻辑
内容的提问来源于stack exchange,提问作者Anurag Parmar
相关产品推荐
相关产品推荐

