Table组件开发中的那些坑我替你踩过了
我的Table组件写法,亲测靠谱
做后台管理系统,Table组件几乎是必选组件,Ant Design的Table用起来确实方便,但我踩了不少坑。最开始我以为只要把数据传进去就行了,结果发现实际项目里各种情况都需要考虑。
先说数据结构吧,我一直坚持一个原则:表格的数据结构要和后端返回的一致,不要在前端做太多转换。这样维护起来轻松,也不会因为数据错乱导致问题。
// 后端返回的数据格式
{
"data": [
{
"id": 1,
"name": "张三",
"email": "zhangsan@example.com",
"status": 1,
"createTime": "2024-01-01"
}
],
"total": 100,
"page": 1,
"pageSize": 10
}
// 我一般这样处理
const [tableData, setTableData] = useState([]);
const [loading, setLoading] = useState(false);
const [pagination, setPagination] = useState({
current: 1,
pageSize: 10,
total: 0
});
const fetchData = async (params) => {
setLoading(true);
try {
const response = await fetch(https://jztheme.com/api/users?page=${params.current}&size=${params.pageSize});
const result = await response.json();
setTableData(result.data);
setPagination(prev => ({
...prev,
current: result.page,
total: result.total
}));
} catch (error) {
console.error('获取数据失败:', error);
} finally {
setLoading(false);
}
};
这种写法的好处是逻辑清晰,加载状态、分页信息都单独管理,不容易出错。我之前见过有人把所有状态放在一个对象里,调试起来简直是噩梦。
列配置这块,我有几条铁律
首先,所有的列配置我都写在外面,不写在组件内部。这样组件渲染的时候就不会重新生成columns数组,避免不必要的重渲染。
const columns = [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
render: (text, record) => (
<span>{text}</span>
)
},
{
title: '邮箱',
dataIndex: 'email',
key: 'email',
width: 200 // 固定宽度,避免列宽频繁变化
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status) => {
const statusMap = {
1: { text: '正常', color: 'green' },
0: { text: '禁用', color: 'red' }
};
return (
<span style={{ color: statusMap[status]?.color }}>
{statusMap[status]?.text}
</span>
);
}
},
{
title: '操作',
key: 'action',
fixed: 'right', // 操作列固定在右侧
width: 150,
render: (_, record) => (
<Space size="middle">
<Button type="link" onClick={() => handleEdit(record)}>编辑</Button>
<Popconfirm
title="确定删除吗?"
onConfirm={() => handleDelete(record.id)}
>
<Button type="link" danger>删除</Button>
</Popconfirm>
</Space>
)
}
];
这里有个坑需要注意:如果在组件内部定义columns,每次渲染都会创建新的对象,即使内容没变也会触发Table重新渲染。这个问题我在项目中遇到过,当时页面卡得不行,查了半天才发现是columns的问题。
分页和排序,别忘了边界处理
分页是最容易出问题的地方,特别是用户快速点击下一页或者输入页码的时候。我一般会加个防抖,避免频繁请求:
import { debounce } from 'lodash';
const handleTableChange = useCallback(debounce((pagination, filters, sorter) => {
const params = {
page: pagination.current,
size: pagination.pageSize,
sortField: sorter.field,
sortOrder: sorter.order
};
fetchData(params);
}, 300), []);
return (
<Table
columns={columns}
dataSource={tableData}
loading={loading}
pagination={pagination}
onChange={handleTableChange}
rowKey="id"
/>
);
另外,排序参数一定要记得处理,我之前就遇到过排序字段传错导致后端报错的情况。sorter.order返回的是’descend’或’ascend’,需要转换成后端需要的格式。
这几种错误写法,别再踩坑了
最常见的错误就是直接把API返回的数据赋值给Table:
// 错误写法
const [data, setData] = useState([]);
useEffect(() => {
fetch('/api/data').then(res => res.json()).then(setData); // 直接赋值,不处理loading状态
}, []);
// 这样写的问题:没有loading状态,用户不知道数据是否在加载中
// 没有错误处理,网络异常时页面会显示空数据
// 没有缓存控制,可能导致重复请求
还有人喜欢在Table组件里面直接处理业务逻辑,比如编辑、删除这些操作,这样组件变得非常臃肿:
// 错误写法
const columns = [
{
title: '操作',
render: (_, record) => (
<Button
onClick={async () => {
// 在这里直接处理业务逻辑,不好维护
try {
await fetch(/api/users/${record.id}, { method: 'DELETE' });
message.success('删除成功');
// 还要更新列表数据...
} catch (error) {
message.error('删除失败');
}
}}
>
删除
</Button>
)
}
];
这种写法的问题是:逻辑分散、难以测试、复用性差。我建议把这些操作提取出来,做成独立的函数。
另一个常见错误是在rowKey上用index,这是大忌:
// 千万别这样做
<Table
rowKey={(record, index) => index} // 用索引作为key会导致更新混乱
dataSource={data}
/>
应该始终使用数据的唯一标识作为rowKey,这样React才能正确识别哪些项被修改了。
实际项目中的坑
表格固定列的性能问题我遇到过多次。当表格有几十列,而且有很多固定列时,滚动会变得很卡。解决办法是减少固定列的数量,或者使用虚拟滚动(不过Ant Design Table暂不支持)。
还有一个坑是搜索条件和表格状态的同步问题。我一般会把搜索表单的状态和表格状态分开管理,然后通过事件通知表格刷新:
// 搜索表单
const [searchForm] = Form.useForm();
const handleSearch = () => {
searchForm.validateFields().then(values => {
// 把搜索条件合并到请求参数中
fetchData({ ...values, page: 1 });
});
};
最后提一下服务端渲染的场景,如果是SSR,Table的初始数据要在服务端就准备好,否则会出现水合不一致的问题。这种情况下我一般会预先把数据注入到页面中。
性能优化要点
表格性能优化主要关注几点:减少不必要的重渲染、合理使用虚拟滚动、避免在render中创建新对象。
对于大数据量的表格,建议分页处理,一次不要加载太多数据。如果真的需要显示大量数据,可以考虑使用react-window这样的虚拟滚动库。
另外,Column里的render函数如果比较复杂,可以用React.memo包装组件来避免不必要的重渲染。
以上是我使用Table组件的一些实战经验,主要还是围绕数据管理和性能优化这两个核心问题。每个人的项目场景不同,可以根据实际情况调整。有更好的方案欢迎评论区交流。

暂无评论