react Antd Table 多选大数据量 UI渲染很慢的解决方案
问题: react 在选择大量的数据后,继续点击页面的选中会出现UI渲染很慢的情况
完全将 UI 隔离的方案
代码:
import { Table, Button, Space, Tag } from 'antd';
import React, { useState, useEffect, useRef, useCallback } from 'react';/*** 模拟 API 请求模块* @param {object} params - 请求参数,包含 page 和 pageSize* @returns {Promise<{records: object[], total: number}>}*/
const mockApi = {fetchList: ({ page, pageSize }) => {console.log(`Fetching data for page: ${page}, pageSize: ${pageSize}`);return new Promise((resolve) => {const totalRecords = 50000; // 模拟一个非常大的数据集const start = (page - 1) * pageSize;// 生成当前页的模拟数据const records = Array.from({ length: pageSize }, (_, i) => {const index = start + i;if (index >= totalRecords) return null; // 避免超出总数return {baseId: `id_${index + 1}`, // 保证 key 的唯一性name: `项目 ${index + 1}`,category: ['A', 'B', 'C'][index % 3],status: ['Active', 'Inactive', 'Archived'][index % 3],};}).filter(Boolean); // 过滤掉 null 值// 模拟网络延迟setTimeout(() => {resolve({ records, total: totalRecords });}, 300);});},
};/*** 高性能跨页选择表格组件*/
function HighPerformanceSelectableTable() {const [loading, setLoading] = useState(false);const [dataSource, setDataSource] = useState([]); // State: 仅存储当前页的表格数据const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 });// 核心 State 1: 存储所有页面已选中的 key,使用 Set 数据结构以获得最佳性能const [allSelectedRowKeys, setAllSelectedRowKeys] = useState(new Set());// 核心 State 2: 仅存储当前页已选中的 key,直接用于驱动 Table UI,保证 UI 响应速度const [currentPageSelectedRowKeys, setCurrentPageSelectedRowKeys] = useState([]);// 核心 Ref: 数据缓存。使用 useRef 创建一个在组件生命周期内持久的 Map 对象// 用于存储所有已加载的数据,且其变化不会触发组件重新渲染const dataCache = useRef(new Map());// 表格列定义const columns = [{ title: '项目 ID', dataIndex: 'baseId', width: 200 },{ title: '项目名称', dataIndex: 'name', width: 250 },{ title: '分类', dataIndex: 'category', width: 150 },{title: '状态',dataIndex: 'status',render: (status) => <Tag color={status === 'Active' ? 'green' : 'red'}>{status}</Tag>,},];// 数据获取函数,使用 useCallback 避免不必要的函数重建const fetchData = useCallback(async (params) => {setLoading(true);const result = await mockApi.fetchList({page: params.current,pageSize: params.pageSize,});setLoading(false);if (result && result.records) {setDataSource(result.records);setPagination((prev) => ({ ...prev, ...params, total: result.total }));// 将新获取的数据存入缓存,以便后续根据 key 能找到完整的 row 数据result.records.forEach((item) => {dataCache.current.set(item.baseId, item);});}}, []);// 首次加载数据useEffect(() => {fetchData({ current: 1, pageSize: 10 });}, [fetchData]);// 处理分页、排序、筛选变化const handleTableChange = (newPagination) => {fetchData({ current: newPagination.current, pageSize: newPagination.pageSize });};// 【关键同步逻辑】// 当 `dataSource` (翻页) 或 `allSelectedRowKeys` (外部操作) 变化时,// 需要重新计算当前页应该展示的勾选状态。useEffect(() => {const keysToShowOnCurrentPage = dataSource.map((item) => item.baseId).filter((key) => allSelectedRowKeys.has(key));setCurrentPageSelectedRowKeys(keysToShowOnCurrentPage);}, [dataSource, allSelectedRowKeys]);// 【核心选择逻辑】// 当用户在表格中进行勾选操作时触发const handleRowSelectionChange = (selectedKeysOnCurrentPage) => {// 步骤 1: 立刻更新当前页的 UI State,确保勾选框的即时响应setCurrentPageSelectedRowKeys(selectedKeysOnCurrentPage);// 步骤 2: 异步更新全局的已选 key 集合 (Set)setAllSelectedRowKeys((prevAllKeys) => {const newAllKeys = new Set(prevAllKeys); // 复制一份,保证 state 的不可变性const currentPageKeys = dataSource.map((item) => item.baseId);// 遍历当前页的所有 keycurrentPageKeys.forEach((key) => {// 如果这个 key 在当前页的最新选中项里,就添加到全局 Set 中if (selectedKeysOnCurrentPage.includes(key)) {newAllKeys.add(key);} else {// 否则(即未选中或被取消选中),就从全局 Set 中移除newAllKeys.delete(key);}});return newAllKeys;});};// Table 的 rowSelection prop 配置const rowSelection = {selectedRowKeys: currentPageSelectedRowKeys, // **关键**: 只将当前页的选中 keys 传给 TableonChange: handleRowSelectionChange,// (可选) 增加 getCheckboxProps 来自定义禁用逻辑// getCheckboxProps: (record) => ({// disabled: record.status === 'Archived',// }),};// 点击按钮获取所有选中项const showAllSelected = () => {// 从 Set 转换为数组const allKeysArray = Array.from(allSelectedRowKeys);// 从缓存中根据 key 查找完整的行数据const allSelectedRows = allKeysArray.map((key) => dataCache.current.get(key)).filter(Boolean);console.group('所有选中项详情');console.log('总数:', allSelectedRows.length);console.log('所有选中的 Keys:', allKeysArray);console.log('所有选中的 Rows:', allSelectedRows);console.groupEnd();// alert(`已选择 ${allSelectedRows.length} 条记录,详情请查看控制台。`);};// 清空所有选择const clearAllSelection = () => {setAllSelectedRowKeys(new Set());// currentPageSelectedRowKeys 会通过 useEffect 自动同步变为空数组};return (<div style={{ padding: '20px' }}><Space direction="vertical" style={{ width: '100%' }}><Space><Button type="primary" onClick={showAllSelected} disabled={allSelectedRowKeys.size === 0}>获取所有选中项</Button><Button onClick={clearAllSelection} disabled={allSelectedRowKeys.size === 0}>清空所有选择</Button></Space><div style={{ fontWeight: 'bold' }}>总共已选择 <Tag color="blue">{allSelectedRowKeys.size}</Tag> 项</div><Tablesize="middle"rowKey="baseId"loading={loading}columns={columns}dataSource={dataSource}rowSelection={rowSelection}pagination={pagination}onChange={handleTableChange}borderedscroll={{y: 'calc(100vh - 615px)',}}/></Space></div>);
}export default HighPerformanceSelectableTable;