Ant Design 3.x Table 大数据量渲染慢,如何优化?
在使用 Ant Design 3.x 的 Table 组件时,当数据量达到数千甚至上万条时,页面渲染会变得非常缓慢,甚至出现卡顿现象。这主要是因为 Table 组件默认会一次性渲染所有数据,导致浏览器 DOM 节点过多,性能急剧下降。本文将介绍几种有效的优化方案。
问题分析
Ant Design 3.x Table 组件在数据量较大时性能瓶颈主要体现在:
一次性渲染大量 DOM 节点,占用大量内存
表格列过多时,每行的单元格数量多,渲染成本高
排序、筛选等操作触发全量数据重新渲染
优化方案
方案一:虚拟滚动(推荐)
虚拟滚动是解决大数据量表格渲染性能问题的最佳方案。它只渲染可视区域内的行,大幅减少 DOM 节点数量。
可以使用第三方库 react-virtualized 或 react-window 来实现虚拟滚动表格。
实现步骤:
安装依赖:npm install react-virtualized --save
自定义表格组件,结合 react-virtualized 的 List 组件实现虚拟滚动
代码示例:
import React from 'react';
import { Table } from 'antd';
import { List } from 'react-virtualized';
import 'react-virtualized/styles.css'; // 引入样式
class VirtualTable extends React.Component {
constructor(props) {
super(props);
this.state = {
dataSource: [], // 原始数据
columns: props.columns,
height: props.height || 400,
rowHeight: props.rowHeight || 50,
};
}
componentDidMount() {
// 模拟加载大量数据
const data = [];
for (let i = 0; i < 10000; i++) {
data.push({
key: i,
name: `姓名${i}`,
age: 20 + Math.floor(Math.random() * 30),
address: `地址信息${i}号`,
});
}
this.setState({ dataSource: data });
}
rowRenderer = ({ index, key, style }) => {
const { dataSource, columns } = this.state;
const rowData = dataSource[index];
return (
<div key={key} style={style} className="virtual-table-row">
<div className="virtual-table-cell">{rowData.key}</div>
{columns.slice(1).map(column => (
<div key={column.key} className="virtual-table-cell">
{rowData[column.dataIndex]}
</div>
))}
</div>
);
};
render() {
const { dataSource, height, rowHeight } = this.state;
return (
<List
width={800}
height={height}
rowCount={dataSource.length}
rowHeight={rowHeight}
rowRenderer={this.rowRenderer}
/>
);
}
}
// 使用示例
const columns = [
{ title: 'ID', dataIndex: 'key', key: 'key' },
{ title: '姓名', dataIndex: 'name', key: 'name' },
{ title: '年龄', dataIndex: 'age', key: 'age' },
{ title: '地址', dataIndex: 'address', key: 'address' },
];
export default () => <VirtualTable columns={columns} />;方案二:分页加载
分页是最简单直接的优化方式,通过限制每次渲染的数据量,减少 DOM 节点数量。
实现方式:
使用 Ant Design 的 Pagination 组件
后端配合实现分页接口,前端传递页码和每页条数
代码示例:
import React, { useState, useEffect } from 'react';
import { Table, Pagination, Button } from 'antd';
const PaginatedTable = () => {
const [dataSource, setDataSource] = useState([]);
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(50);
const [total, setTotal] = useState(0);
const columns = [
{ title: 'ID', dataIndex: 'id', key: 'id' },
{ title: '姓名', dataIndex: 'name', key: 'name' },
{ title: '年龄', dataIndex: 'age', key: 'age' },
{ title: '地址', dataIndex: 'address', key: 'address' },
];
// 模拟加载数据
const fetchData = async (page, size) => {
setLoading(true);
try {
// 模拟API请求延迟
await new Promise(resolve => setTimeout(resolve, 500));
// 模拟生成数据
const data = [];
const start = (page - 1) * size;
const end = start + size;
for (let i = start; i < end; i++) {
data.push({
id: i + 1,
name: `用户${i + 1}`,
age: 20 + Math.floor(Math.random() * 30),
address: `城市${i + 1}区街道${i + 1}号`,
});
}
setDataSource(data);
setTotal(10000); // 假设总共有10000条数据
} catch (error) {
console.error('加载数据失败:', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData(currentPage, pageSize);
}, [currentPage, pageSize]);
const handlePageChange = (page, size) => {
setCurrentPage(page);
setPageSize(size);
};
return (
<div>
<Table
columns={columns}
dataSource={dataSource}
loading={loading}
pagination={false} // 隐藏默认分页
bordered
/>
<Pagination
current={currentPage}
pageSize={pageSize}
total={total}
onChange={handlePageChange}
onShowSizeChange={handlePageChange}
showSizeChanger
showQuickJumper
showTotal={(total, range) => `第 ${range[0]}-${range[1]} 条/共 ${total} 条`}
style={{ marginTop: 16, textAlign: 'right' }}
/>
</div>
);
};
export default PaginatedTable;方案三:懒加载与无限滚动
对于需要连续浏览大量数据的场景,可以实现无限滚动,当用户滚动到页面底部时自动加载更多数据。
实现思路:
监听表格容器的滚动事件
判断是否滚动到底部
加载下一页数据并追加到现有数据中
代码示例:
import React, { useState, useEffect, useRef } from 'react';
import { Table } from 'antd';
const InfiniteScrollTable = () => {
const [dataSource, setDataSource] = useState([]);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const [page, setPage] = useState(1);
const containerRef = useRef(null);
const columns = [
{ title: 'ID', dataIndex: 'id', key: 'id' },
{ title: '姓名', dataIndex: 'name', key: 'name' },
{ title: '年龄', dataIndex: 'age', key: 'age' },
{ title: '地址', dataIndex: 'address', key: 'address' },
];
// 模拟加载数据
const loadMoreData = async () => {
if (loading || !hasMore) return;
setLoading(true);
try {
// 模拟API请求延迟
await new Promise(resolve => setTimeout(resolve, 500));
// 模拟生成数据
const newData = [];
const pageSize = 50;
const start = (page - 1) * pageSize;
const end = start + pageSize;
for (let i = start; i < end; i++) {
newData.push({
id: i + 1,
name: `用户${i + 1}`,
age: 20 + Math.floor(Math.random() * 30),
address: `城市${i + 1}区街道${i + 1}号`,
});
}
setDataSource(prev => [...prev, ...newData]);
// 假设总共只有2000条数据
if (end >= 2000) {
setHasMore(false);
} else {
setPage(prev => prev + 1);
}
} catch (error) {
console.error('加载数据失败:', error);
} finally {
setLoading(false);
}
};
// 初始加载
useEffect(() => {
loadMoreData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// 监听滚动事件
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const handleScroll = () => {
const { scrollTop, scrollHeight, clientHeight } = container;
// 距离底部100px时开始加载
if (scrollTop + clientHeight >= scrollHeight - 100 && hasMore && !loading) {
loadMoreData();
}
};
container.addEventListener('scroll', handleScroll);
return () => container.removeEventListener('scroll', handleScroll);
}, [hasMore, loading]);
return (
<div
ref={containerRef}
style={{ height: '500px', overflow: 'auto', border: '1px solid #e8e8e8' }}
>
<Table
columns={columns}
dataSource={dataSource}
loading={loading && dataSource.length === 0}
pagination={false}
bordered
/>
{loading && dataSource.length > 0 && (
<div style={{ textAlign: 'center', padding: '16px' }}>
加载中...
</div>
)}
{!hasMore && dataSource.length > 0 && (
<div style={{ textAlign: 'center', padding: '16px', color: '#999' }}>
没有更多数据了
</div>
)}
</div>
);
};
export default InfiniteScrollTable;方案四:优化表格配置
除了上述三种主要方案外,还可以通过一些配置优化来提升表格性能:
1. 固定列优化
如果表格有固定列,尽量减少固定列的数量,因为固定列会增加渲染复杂度。
2. 简化列配置
避免在列配置中使用复杂的 render 函数,尽量保持 render 函数简洁。
// 不推荐:复杂的render函数
{
title: '操作',
key: 'action',
render: (text, record) => (
<div>
<Button type="link" onClick={() => handleEdit(record)}>编辑</Button>
<Button type="link" danger onClick={() => handleDelete(record)}>删除</Button>
<Dropdown overlay={menu} placement="bottomRight">
<Button type="link">更多</Button>
</Dropdown>
</div>
)
}
// 推荐:简化render函数或使用其他方式
{
title: '操作',
key: 'action',
render: (text, record) => (
<a onClick={() => handleEdit(record)}>编辑</a>
)
}3. 关闭不必要的特性
如果不需要某些特性,可以通过配置关闭:
关闭动画效果:设置
transitionName=""关闭斑马纹:设置
striped=false简化表格样式,减少CSS复杂度
4. 使用 rowKey 属性
确保设置了唯一的 rowKey,避免React使用索引作为key导致的性能问题。
<Table
dataSource={dataSource}
columns={columns}
rowKey="id" // 使用唯一标识作为rowKey
/>总结
针对 Ant Design 3.x Table 大数据量渲染慢的问题,推荐优先考虑以下方案:
虚拟滚动:适用于需要展示大量数据且需要快速滚动的场景,性能最佳
分页加载:实现简单,兼容性好,适用于大多数业务场景
无限滚动:适用于需要连续浏览数据的场景,用户体验较好
在实际应用中,可以根据具体业务需求选择合适的优化方案,也可以组合使用多种方案以达到最佳性能。同时,注意对表格配置进行优化,减少不必要的渲染开销。