You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何防止React组件重渲染时滚动位置被重置

自定义列筛选弹窗操作后滚动条自动回到顶部问题解决方案

核心原因

出现该问题的本质是勾选/取消筛选选项时触发状态更新,导致自定义弹窗DOM被销毁重挂载,而非正常的属性更新重渲染,常见触发场景包括:

  • 将自定义弹窗的组件定义直接写在Data Grid的插槽(slots)配置、父组件render函数内部,每次表格状态变更(筛选值修改属于Data Grid受控状态更新),插槽函数重新执行,弹窗DOM被销毁后重建,浏览器默认将新生成的滚动容器定位到顶部。
  • 给弹窗组件配置了动态变化的key属性,每次渲染key值变动触发组件强制重挂载。
  • 传给弹窗的事件回调没有做引用固定,每次父组件渲染生成新的函数引用,触发子组件意外卸载重载。

优先方案:从渲染逻辑层面规避问题(无需手动维护滚动位置)

这是最彻底的解决方式,从根源上避免弹窗无意义重挂载:

  • 将自定义筛选弹窗的组件定义抽离到Data Grid父组件的渲染逻辑外层,不要在slots配置项、父组件return的JSX中内联定义弹窗组件。
  • 所有传给弹窗的事件回调(勾选状态变更、筛选确认/重置等)用useCallback包裹,确保多次渲染间函数引用稳定。
  • 移除弹窗组件上不必要的动态key配置,避免key值变动触发强制重挂载。
    参考实现代码:
// 将自定义弹窗组件抽离到父组件外部定义,不要嵌套写在父组件内部
const CustomColumnFilterPanel = React.forwardRef((props, ref) => {
  // 弹窗内部筛选逻辑、选项渲染逻辑
  return (
    <div ref={ref} className="custom-filter-panel">
      {/* 筛选选项列表 */}
    </div>
  )
});

function DataGridPage() {
  // 用useCallback固定事件回调引用
  const handleFilterValueChange = useCallback((newFilterModel) => {
    setTableFilterModel(newFilterModel);
  }, []);

  return (
    <DataGrid
      filterModel={tableFilterModel}
      slots={{
        // 直接引用外层定义的组件,禁止在这里内联写组件定义
        columnsPanel: CustomColumnFilterPanel
      }}
      slotProps={{
        columnsPanel: {
          onFilterChange: handleFilterValueChange
        }
      }}
    />
  )
}

兜底方案:重渲染后自动恢复滚动位置

如果受业务逻辑限制,无法完全避免弹窗重挂载,可以通过手动记录、恢复滚动位置实现无感知体验:

  • 给弹窗的滚动容器绑定DOM ref。
  • 监听滚动容器的scroll事件,将实时滚动位置存在非响应式ref中,不要用普通state存储滚动值,避免触发循环重渲染。
  • 在组件每次挂载、更新完成后的useEffect执行时机,将存储的滚动值重新赋值给滚动容器,恢复之前的浏览位置。
    参考实现代码:
const CustomFilterPanel = (props) => {
  const scrollContainerRef = useRef(null);
  // 用ref存储滚动位置,不会触发组件重渲染
  const savedScrollTop = useRef(0);

  // 组件每次更新/挂载后恢复滚动位置
  useEffect(() => {
    if (scrollContainerRef.current) {
      scrollContainerRef.current.scrollTop = savedScrollTop.current;
    }
  });

  const handleContainerScroll = useCallback((e) => {
    savedScrollTop.current = e.target.scrollTop;
  }, []);

  return (
    <div 
      ref={scrollContainerRef}
      onScroll={handleContainerScroll}
      style={{ maxHeight: 320, overflowY: 'auto' }}
    >
      {/* 筛选选项内容 */}
    </div>
  )
}

注意:手动恢复滚动位置属于兼容兜底逻辑,性能和体验弱于从渲染结构层面解决重挂载问题,优先推荐使用第一种方案。

内容的提问来源于stack exchange,提问作者Caleb Koch

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 19:21:37