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

相关推荐
技术砖家--Felix35 分钟前
Spring Boot Web开发篇:构建RESTful API
前端·spring boot·restful
yume_sibai2 小时前
TS 常用内置方法
前端·javascript·typescript
新知图书2 小时前
ArkTS语言、基本组成与数据类型
前端·javascript·typescript
嘗_2 小时前
手写自己的小型react
前端·javascript·react.js
嘀咕博客2 小时前
h5游戏免费下载:HTML5拉杆子过关小游戏
前端·游戏·html5
Moonbit2 小时前
MoonBit 推出 LLVM Debugger,核心用户数破十万
前端·编程语言·llvm
zuo-yiran2 小时前
vue div标签可输入状态下实现数据双向绑定
前端·javascript·vue.js
qq_316837752 小时前
使用leader-line-vue 时垂直元素间距过小连线打转的解决
前端·javascript·vue.js
天天向上10242 小时前
vue3使用ONLYOFFICE 实现在线Word,Excel等文档
前端·javascript·html
KoProject2 小时前
发布30款App之后,我总结了这套GLM-4.6全自动化开发流
前端·后端·github