React页面跳转返回不丢失数据与滚动位置的实现方案
解决方案:React分页列表跳转返回后恢复状态(无全量数据存储)
针对分页加载的列表页跳转详情页返回后,需要保留已加载数据、恢复滚动位置且不存储全量数据的需求,以下是两种简洁的React实现方案:
方案一:基于sessionStorage的临时缓存(推荐,支持页面刷新)
利用sessionStorage存储已加载的分页数据、当前页码和滚动位置,组件挂载时自动恢复状态,避免重复请求已加载过的数据。
代码实现
import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; const PAGE_SIZE = 10; // 每页数据量 function ListPage() { const navigate = useNavigate(); const [listData, setListData] = useState([]); const [currentPage, setCurrentPage] = useState(1); // 组件挂载时恢复状态 useEffect(() => { // 读取缓存的列表数据、页码和滚动位置 const savedData = sessionStorage.getItem('listPageData'); const savedPage = sessionStorage.getItem('listCurrentPage'); const savedScroll = sessionStorage.getItem('listScrollPosition'); if (savedData) { setListData(JSON.parse(savedData)); } if (savedPage) { setCurrentPage(Number(savedPage)); } // 恢复滚动位置 if (savedScroll) { window.scrollTo(0, Number(savedScroll)); } else { // 首次加载时请求第一页数据 fetchPage(1); } }, []); // 状态更新时同步缓存到sessionStorage useEffect(() => { sessionStorage.setItem('listPageData', JSON.stringify(listData)); sessionStorage.setItem('listCurrentPage', currentPage.toString()); }, [listData, currentPage]); // 监听滚动,实时记录滚动位置 useEffect(() => { const handleScroll = () => { sessionStorage.setItem('listScrollPosition', window.scrollY.toString()); }; window.addEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll); }, []); // 请求指定页码的分页数据 const fetchPage = async (page) => { const response = await fetch(`/api/list?page=${page}&size=${PAGE_SIZE}`); const newData = await response.json(); // 追加新数据到现有列表 setListData(prev => [...prev, ...newData]); setCurrentPage(page); }; // 加载更多按钮逻辑 const handleLoadMore = () => { fetchPage(currentPage + 1); }; // 跳转到详情页 const handleGoToDetail = (itemId) => { navigate(`/detail/${itemId}`); }; return ( <div> <ul> {listData.map(item => ( <li key={item.id} onClick={() => handleGoToDetail(item.id)}> {item.title} </li> ))} </ul> <button onClick={handleLoadMore}>加载更多</button> </div> ); } export default ListPage;
方案优势
- 仅缓存已加载的分页数据,不会存储全量数据,适配生产环境大数据量场景
sessionStorage为标签页隔离存储,多标签页场景互不干扰- 支持页面刷新后恢复状态,稳定性更强
方案二:基于React Router状态传递(临时跳转场景)
如果不需要页面刷新后保留状态,可以通过useNavigate的state参数传递当前列表状态,返回时直接读取路由状态恢复。
代码实现
import { useState, useEffect } from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; const PAGE_SIZE = 10; function ListPage() { const navigate = useNavigate(); const location = useLocation(); const [listData, setListData] = useState([]); const [currentPage, setCurrentPage] = useState(1); // 从路由状态恢复数据 useEffect(() => { if (location.state?.listData) { setListData(location.state.listData); setCurrentPage(location.state.currentPage); window.scrollTo(0, location.state.scrollPosition); } else { fetchPage(1); } }, [location.state]); const fetchPage = async (page) => { const response = await fetch(`/api/list?page=${page}&size=${PAGE_SIZE}`); const newData = await response.json(); setListData(prev => [...prev, ...newData]); setCurrentPage(page); }; const handleLoadMore = () => { fetchPage(currentPage + 1); }; // 跳转时携带当前状态 const handleGoToDetail = (itemId) => { navigate(`/detail/${itemId}`, { state: { listData, currentPage, scrollPosition: window.scrollY } }); }; // 其余渲染逻辑同方案一 return ( <div> <ul> {listData.map(item => ( <li key={item.id} onClick={() => handleGoToDetail(item.id)}> {item.title} </li> ))} </ul> <button onClick={handleLoadMore}>加载更多</button> </div> ); } export default ListPage;
方案特点
- 无需依赖本地存储,状态随路由跳转临时携带
- 页面刷新后状态会丢失,适合不需要持久化的临时跳转场景
注意事项
sessionStorage有大小限制(通常为5MB),如果单页数据量较大,可设置最大缓存页数,超过后删除最早加载的页面数据- 若需要跨标签页共享状态,可替换为
localStorage,但需注意清理过期数据 - 可封装自定义Hook(如
usePagedListCache)复用缓存和状态恢复逻辑,减少代码冗余
内容的提问来源于stack exchange,提问作者Duck Dodgers
相关产品推荐
相关产品推荐

