问题: react 在选择大量的数据后,继续点击页面的选中会出现UI渲染很慢的情况
完全将 UI 隔离的方案
代码:
js
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);
// 遍历当前页的所有 key
currentPageKeys.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 传给 Table
onChange: 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>
<Table
size="middle"
rowKey="baseId"
loading={loading}
columns={columns}
dataSource={dataSource}
rowSelection={rowSelection}
pagination={pagination}
onChange={handleTableChange}
bordered
scroll={{
y: 'calc(100vh - 615px)',
}}
/>
</Space>
</div>
);
}
export default HighPerformanceSelectableTable;