Table组件开发中的那些坑我替你踩过了

设计师海利 组件 阅读 1,096
赞 16 收藏
二维码
手机扫码查看
反馈

我的Table组件写法,亲测靠谱

做后台管理系统,Table组件几乎是必选组件,Ant Design的Table用起来确实方便,但我踩了不少坑。最开始我以为只要把数据传进去就行了,结果发现实际项目里各种情况都需要考虑。

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组件的一些实战经验,主要还是围绕数据管理和性能优化这两个核心问题。每个人的项目场景不同,可以根据实际情况调整。有更好的方案欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论