React重渲染时如何避免未变更组件的滚动条被重置到顶部
解决方案
方案1:阻止SecondComponent不必要重渲染(最优解)
导致问题的核心原因是父组件状态更新时,所有子组件默认都会跟随重渲染,哪怕子组件的props没有实际变化。你可以通过React.memo + React.useCallback的组合跳过SecondComponent的重渲染:
- 用
React.memo包裹SecondComponent,开启props浅比较,只有props变化时才触发重渲染:
const SecondComponent = React.memo((props) => { // 原有SecondComponent的业务逻辑保持不变 })
- 父组件中用
React.useCallback缓存onChange回调,避免每次父组件渲染都生成新的函数引用(React保证state的setter函数引用永远稳定,所以依赖项可以留空):
const A = () => { const [value1, setValue1] = React.useState(true); const [value2, setValue2] = React.useState(true); // 缓存onChange回调 const handleChange = React.useCallback(() => { setValue1(false); setValue2(false); }, []) return ( <div> <div className="first-comp"> <FirstComponent data1={value1} data2={value2} /> </div> <div className="second-comp"> <SecondComponent onChange={handleChange} /> </div> </div> ) }
调整后SecondComponent的props不会发生变化,会直接跳过重渲染,滚动位置自然不会被重置。
方案2:手动保存/恢复滚动位置
如果业务逻辑不允许阻止SecondComponent重渲染,可以在重渲染前后手动保存、恢复滚动位置:
- 用
React.forwardRef给SecondComponent的内部滚动容器挂载ref:
const SecondComponent = React.forwardRef((props, ref) => { // 把ref绑定到内部产生滚动的DOM节点上 return <div ref={ref} className="second-comp-scroll-area"> {/* 原有组件内容 */} </div> })
- 父组件中存储滚动位置,组件更新后恢复:
const A = () => { const [value1, setValue1] = React.useState(true); const [value2, setValue2] = React.useState(true); const secondCompRef = React.useRef(null); const savedScrollTop = React.useRef(0); const handleChange = () => { // 触发状态更新前先保存当前滚动位置 if(secondCompRef.current) { savedScrollTop.current = secondCompRef.current.scrollTop } setValue1(false); setValue2(false); } // 状态更新完成后恢复滚动位置 React.useEffect(() => { if(secondCompRef.current && savedScrollTop.current) { secondCompRef.current.scrollTop = savedScrollTop.current } }, [value1, value2]) return ( <div> <div className="first-comp"> <FirstComponent data1={value1} data2={value2} /> </div> <div className="second-comp"> <SecondComponent ref={secondCompRef} onChange={handleChange} /> </div> </div> ) }
额外排查点
如果调整后SecondComponent仍然出现不必要重渲染,可以检查两个点:
- 传给SecondComponent的其他props是不是每次渲染都会生成新的引用(比如对象、数组没有用
useMemo缓存) - SecondComponent内部如果有列表渲染,是不是用了索引作为key,key不稳定也会导致DOM重新生成、滚动条复位。
内容的提问来源于stack exchange,提问作者Harsh Agarwal
相关产品推荐
相关产品推荐

