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,也可以应用于其他表格组件的性能优化场景。 这些优化手段使应用能够处理万级数据记录的同时保持流畅的用户交互体验,显著提升数据密集型应用的性能表现。

相关推荐
右子6 小时前
理解响应式设计—理念、实践与常见误解
前端·后端·响应式设计
KaiSonng6 小时前
【前端利器】这款轻量级图片标注库让你的Web应用瞬间提升交互体验
前端
二十雨辰6 小时前
vite性能优化
前端·vue.js
明月与玄武6 小时前
浅谈 富文本编辑器
前端·javascript·vue.js
paodan6 小时前
如何使用ORM 工具,Prisma
前端
布列瑟农的星空6 小时前
重学React——memo能防止Context的额外渲染吗
前端
FuckPatience6 小时前
Vue 与.Net Core WebApi交互时路由初探
前端·javascript·vue.js
小小前端_我自坚强6 小时前
前端踩坑指南 - 避免这些常见陷阱
前端·程序员·代码规范
lichenyang4536 小时前
从零实现JSON与图片文件上传功能
前端