Next.js App Router中调用router.refresh()时如何保留滚动状态
解决Next.js 13 App Router中
router.refresh()重置滚动位置的问题 针对你遇到的问题,提供三种可行的解决方案:
方案一:手动保存并恢复滚动位置
在调用router.refresh()前后,手动记录和恢复列表容器的滚动位置,是最直接的临时修复方案:
'use client'; import { useRouter } from 'next/navigation'; import { useState, useEffect, useRef } from 'react'; export default function StarButton({ itemId }: { itemId: string }) { const router = useRouter(); // 绑定到列表容器的DOM引用 const listContainerRef = useRef<HTMLDivElement>(null); const [savedScrollTop, setSavedScrollTop] = useState(0); const handleStarToggle = async () => { // 1. 点击星标前,保存当前滚动位置 if (listContainerRef.current) { setSavedScrollTop(listContainerRef.current.scrollTop); } // 2. 执行数据库更新逻辑 await fetch(`/api/toggle-star/${itemId}`, { method: 'POST' }); // 3. 触发服务端组件刷新 router.refresh(); }; // 4. 刷新完成后恢复滚动位置 useEffect(() => { if (listContainerRef.current && savedScrollTop > 0) { listContainerRef.current.scrollTop = savedScrollTop; // 重置状态,避免路由变化时重复触发 setSavedScrollTop(0); } }, [router, savedScrollTop]); return ( <> {/* 列表容器,绑定ref */} <div ref={listContainerRef} className="h-[600px] overflow-y-auto"> {/* 你的卡片列表 */} </div> <button onClick={handleStarToggle} className="cursor-pointer">⭐</button> </> ); }
方案二:改用客户端局部更新(推荐)
放弃router.refresh(),改用客户端数据缓存工具(如SWR/React Query)实现局部列表更新,完全避免页面级刷新带来的滚动重置:
以SWR为例:
'use client'; import useSWR from 'swr'; // 数据获取函数 const fetchItems = () => fetch('/api/items').then(res => res.json()); export default function ItemList() { // 用SWR管理列表数据缓存 const { data: items, mutate } = useSWR('items-list', fetchItems); const handleStarToggle = async (itemId: string) => { // 1. 先更新数据库 await fetch(`/api/toggle-star/${itemId}`, { method: 'POST' }); // 2. 触发缓存失效,重新获取最新列表数据 await mutate(); // 可选:如果需要即时视觉反馈,可以手动调整items数组顺序,把星标项移到顶部 }; if (!items) return <div>加载中...</div>; return ( <div className="h-[600px] overflow-y-auto"> {items.map(item => ( <div key={item.id} className="card"> <button onClick={() => handleStarToggle(item.id)}> {item.isStarred ? '⭐' : '☆'} </button> {/* 卡片内容 */} </div> ))} </div> ); }
这种方案的优势是用户体验流畅,没有页面刷新的闪烁,也不会影响滚动位置。
方案三:使用revalidateTag精准刷新
如果你的服务端组件数据获取使用了Tagged Cache,可以用revalidateTag替代router.refresh(),只刷新指定标签的缓存数据,避免全页刷新:
服务端组件(列表页面):
// app/items/page.tsx export default async function ItemsPage() { // 给数据请求标记标签 const items = await fetch('/api/items', { next: { tags: ['user-items'] } }).then(res => res.json()); // 将数据传递给客户端组件渲染 return <ItemList items={items} />; }
客户端组件(星标按钮):
'use client'; import { revalidateTag } from 'next/cache'; export default function StarButton({ itemId }: { itemId: string }) { const handleStarToggle = async () => { await fetch(`/api/toggle-star/${itemId}`, { method: 'POST' }); // 仅重新验证标记为'user-items'的缓存数据 revalidateTag('user-items'); }; return <button onClick={handleStarToggle}>⭐</button>; }
revalidateTag只会触发对应标签数据的重新获取,不会导致页面滚动位置重置,同时保持服务端组件的数据新鲜度。
内容的提问来源于stack exchange,提问作者suamikim
相关产品推荐
相关产品推荐

