ProTable 大数据渲染优化:实现高性能表格编辑

ProTable 大数据渲染优化:实现高性能表格编辑

本文将介绍如何通过多种技术手段优化 Ant Design ProTable 组件在处理大规模数据时的性能问题,实现流畅的单元格编辑体验。

优化前的问题分析

当表格需要渲染大量数据时(如 5000+ 单元格),通常会出现:

  • 页面滚动明显卡顿
  • 交互响应迟缓
  • 内存占用过高
  • 全表不必要的重新渲染

核心优化方案

1. 智能单元格组件 (SmartCell)

通过精细化状态管理,避免不必要的全局重新渲染:

ini 复制代码
import "./SmartCell.less";
import { Input } from "antd";
import { useState, useEffect, useRef } from "react";

// 全局存储上一个操作对象
let previousOperatedNode = { current: null };

function SmartCell(props: any) {
  const [isActive, setIsActive] = useState(false);
  const [editingKey, setEditingKey] = useState("");
  const { record, columnInfo, updateDataSource, dataSource } = props;
  const currentOperatedNode = useRef(null);

  // 暴露操作DOM的方法
  useEffect(() => {
    const clearSelection = () => {
      setIsActive(false);
    };
    
    const clearEditState = () => {
      setEditingKey("");
    };
    
    currentOperatedNode.current = { clearSelection, clearEditState };
  }, []);

  const uniqueCellKey = `${columnInfo.dataIndex}_${record.key}`;

  // 处理单元格点击
  const handleClick = () => {
    if (previousOperatedNode.current && 
        previousOperatedNode.current !== currentOperatedNode.current) {
      previousOperatedNode?.current?.clearSelection();
    }
    setIsActive(true);
    previousOperatedNode.current = currentOperatedNode.current;
  };

  // 处理单元格双击
  const handleDoubleClick = () => {
    if (previousOperatedNode.current && 
        previousOperatedNode.current !== currentOperatedNode.current) {
      previousOperatedNode?.current?.clearEditState();
    }
    setEditingKey(uniqueCellKey);
    previousOperatedNode.current = currentOperatedNode.current;
  };

  // 保存编辑内容
  const handleSave = (e) => {
    const value = e.target.value;
    const updatedDataSource = dataSource.map(item => ({
      ...item,
      [columnInfo.dataIndex]: item.key === record.key ? value : item[columnInfo.dataIndex]
    }));
    updateDataSource(updatedDataSource);
    setEditingKey("");
  };

  const cellContent = (
    <div
      onClick={handleClick}
      onDoubleClick={handleDoubleClick}
      className={isActive ? "active-cell" : "normal-cell"}
    >
      {record[columnInfo.dataIndex]}
    </div>
  );

  return editingKey === uniqueCellKey ? (
    <Input 
      defaultValue={record[columnInfo.dataIndex]} 
      onPressEnter={handleSave} 
      autoFocus
    />
  ) : (
    cellContent
  );
}

export default SmartCell;

2. 渲染性能优化

通过自定义更新逻辑防止不必要的重新渲染:

scala 复制代码
class SmartCellOptimized extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    // 仅当单元格值发生变化时才重新渲染
    return this.props.value !== nextProps.value || 
           this.props.isActive !== nextProps.isActive;
  }
  
  render() {
    return <SmartCell {...this.props} />;
  }
}

export default SmartCellOptimized;

3. 可视区域动态渲染

使用 Intersection Observer API 实现只渲染可见区域内容:

ini 复制代码
export const ViewportObserver = ({
  children,
  onVisibilityChange
}: {
  children: ReactNode;
  onVisibilityChange?: (isVisible: boolean) => void;
}) => {
  const [isInViewport, setIsInViewport] = useState(false);
  const [throttledVisibilityCheck] = useState(() => throttle(setIsInViewport, 100));
  
  const containerRef = useRef<HTMLDivElement | null>(null);
  const observerRef = useRef<IntersectionObserver | null>(null);

  useEffect(() => {
    const observer = new IntersectionObserver((entries) => {
      const [entry] = entries;
      throttledVisibilityCheck(entry.isIntersecting);
      onVisibilityChange?.(entry.isIntersecting);
    }, {
      threshold: 0.1,
      rootMargin: '50px 0px'
    });
    
    if (containerRef.current) {
      observer.observe(containerRef.current);
    }
    
    observerRef.current = observer;
    
    return () => {
      if (containerRef.current) {
        observer.unobserve(containerRef.current);
      }
      observer.disconnect();
    };
  }, []);

  return (
    <div 
      ref={containerRef}
      style={{ width: '100%', height: '100%', minHeight: '40px' }}
    >
      {isInViewport ? children : <div className="cell-placeholder" />}
    </div>
  );
};

完整集成示例

ini 复制代码
import { ProTable } from "@ant-design/pro-components";
import { useState } from "react";
import SmartCell from "./SmartCell";
import ViewportObserver from "./ViewportObserver";

const dataSource = []; // 您的数据源
const columns = []; // 您的列配置

const OptimizedTable = () => {
  const [tableData, setTableData] = useState(dataSource);

  const optimizedColumns = columns.map(column => ({
    ...column,
    render: (text, record) => (
      <ViewportObserver>
        <SmartCell
          record={record}
          columnInfo={column}
          dataSource={tableData}
          updateDataSource={setTableData}
        />
      </ViewportObserver>
    )
  }));

  return (
    <ProTable
      dataSource={tableData}
      columns={optimizedColumns}
      rowKey="key"
      pagination={{ pageSize: 100 }}
      scroll={{ x: 3000, y: 600 }}
      sticky
    />
  );
};

export default OptimizedTable;

优化效果对比

优化阶段 渲染性能 内存占用 用户体验
未优化 严重卡顿 不可接受
部分优化 有所改善 中等 基本可用
全优化 流畅 良好体验

最佳实践建议

  1. ​数据分页​:结合分页加载,减少单次渲染数据量
  2. ​虚拟滚动​:使用专业虚拟滚动库如 react-window
  3. ​性能监控​:使用 React DevTools 分析组件渲染次数
  4. ​渐进加载​:先渲染可见区域,滚动时再加载其他内容
  5. ​缓存策略​:对已渲染的单元格内容进行适当缓存

总结

通过智能单元格组件、渲染优化和可视区域动态渲染三项关键技术,可以有效解决 ProTable 在大数据量下的性能问题。这些优化策略不仅适用于 Ant Design ProTable,也可以应用于其他表格组件的性能优化场景。 这些优化手段使应用能够处理万级数据记录的同时保持流畅的用户交互体验,显著提升数据密集型应用的性能表现。

相关推荐
胡志辉的博客4 分钟前
【完全开源】IP 纯净度检测 可一键部署到自己的CF
前端·javascript·chrome·ip·chromium
Hilaku31 分钟前
作为面试官,我最怕遇到什么样的候选人?
前端·javascript·程序员
TiDi1 小时前
Pinia优化重复请求
前端
web3d5201 小时前
01-用 Leafletjs 10 分钟搭一张水利一张图(Vue3 + Vite 实战)
前端·javascript
晚安日记wanna1 小时前
Vue2 的 defineProperty 差在哪四层追问筛掉九成候选人
前端·vue.js·面试
TiDi1 小时前
吸顶导航交互实现
前端
kisshyshy1 小时前
从Props透传到自定义Hook:系统梳理React跨层级通信与逻辑复用
前端·架构·代码规范
yume_sibai1 小时前
05-Flutter实战项目
前端·flutter
涛涛ing2 小时前
React 19.3 发布,但真正的赢家是 StyleX:当 AI 成为框架的“第一用户”
前端
zhangminghuan2 小时前
Vue vs React 实战拓展面试题|摒弃基础八股,聚焦业务开发核心难点(2026前端进阶)
前端