React-window:如何避免列表随状态变更重渲染并保持滚动位置?
列表状态变更后滚动位置重置?原因和解决办法看这里!
嘿,这个问题我之前做React项目时也踩过坑,咱们一步步来拆解清楚~
一、为啥会出现滚动回到顶部的情况?
本质上是列表或者它的容器在状态更新时被重新创建/完全重渲染了,浏览器会把新渲染的DOM容器的滚动位置默认重置为0。常见的触发场景有这几个:
- 列表组件没做渲染优化,父组件状态一变,整个列表的所有DOM都被销毁后重新生成
- 列表项的
key用了不稳定的值(比如循环索引、每次渲染都变的随机数),React会把这些列表项当成全新的元素,直接全部重渲染 - 滚动容器本身被包裹在条件渲染里,状态一变容器就被卸载再重新挂载,自然滚动位置就没了
二、怎么保持滚动位置+避免不必要重渲染?
1. 先给列表做渲染优化,减少无意义重渲染
首先用React.memo包裹列表项组件,让它只在props变化时才更新,别跟着父组件瞎凑热闹:
// 列表项组件,用React.memo包裹做浅比较 const ListItem = React.memo(({ item }) => { return <div className="list-item">{item.content}</div>; });
然后给每个列表项设置稳定唯一的key,一定要用数据本身的唯一ID,别用索引!比如你的数据里有id字段就用它:
{listData.map(item => ( <ListItem key={item.id} item={item} /> ))}
2. 手动保存+恢复滚动位置
如果实在没法避免容器重渲染,那就手动把滚动位置存起来,渲染完再恢复回去:
方法一:用useRef存容器和滚动位置,结合useEffect恢复
function ScrollableList() { // 存滚动容器的DOM引用 const listContainerRef = useRef(null); // 存滚动位置,用useRef保证状态不会触发重渲染 const savedScrollPosition = useRef(0); // 列表数据变更前,先把当前滚动位置存起来 useEffect(() => { if (listContainerRef.current) { savedScrollPosition.current = listContainerRef.current.scrollTop; } }, [listData]); // 依赖列表数据,数据变之前执行 // 列表渲染完成后,把滚动位置恢复回去 useEffect(() => { if (listContainerRef.current) { listContainerRef.current.scrollTop = savedScrollPosition.current; } }, [listData]); return ( <div ref={listContainerRef} style={{ height: '400px', overflowY: 'auto', border: '1px solid #eee' }} > {listData.map(item => ( <ListItem key={item.id} item={item} /> ))} </div> ); }
方法二:用useLayoutEffect更及时恢复,避免视觉跳动
如果用上面的方法还是能看到滚动跳一下的情况,就把恢复滚动的useEffect换成useLayoutEffect,它会在DOM更新完成后同步执行,比useEffect早一步,视觉上更流畅:
useLayoutEffect(() => { if (listContainerRef.current) { listContainerRef.current.scrollTop = savedScrollPosition.current; } }, [listData]);
3. 别让滚动容器被反复挂载卸载
检查下你的代码里有没有这种情况:用条件渲染直接控制滚动容器的显示隐藏,比如:
// 这种写法会让容器反复卸载挂载,滚动位置肯定没了 {isListVisible && <div ref={listContainerRef}>...</div>}
改成用style.display来控制显示隐藏,这样容器的DOM会一直保留,滚动位置自然也不会丢:
<div ref={listContainerRef} style={{ display: isListVisible ? 'block' : 'none', height: '400px', overflowY: 'auto' }} > ... </div>
三、额外小提示
如果你的列表数据特别多,用了虚拟滚动库(比如react-window、react-virtualized),这些库本身就自带了滚动位置保持的逻辑,只要你正确配置了列表项的key和数据源,根本不用自己手动处理~
内容的提问来源于stack exchange,提问作者My real name
相关产品推荐
相关产品推荐

