Table表格

展示行列数据。

设计师专属

安装 Kitchen Sketch 插件 💎,两步就可以自动生成 Ant Design 表格组件。

何时使用

  • 当有大量结构化的数据需要展现时;

  • 当需要对数据进行排序、搜索、分页、自定义操作等复杂行为时。

如何使用

指定表格的数据源 dataSource 为一个数组。

  1. const dataSource = [
  2. {
  3. key: '1',
  4. name: '胡彦斌',
  5. age: 32,
  6. address: '西湖区湖底公园1号',
  7. },
  8. {
  9. key: '2',
  10. name: '胡彦祖',
  11. age: 42,
  12. address: '西湖区湖底公园1号',
  13. },
  14. ];
  15. const columns = [
  16. {
  17. title: '姓名',
  18. dataIndex: 'name',
  19. key: 'name',
  20. },
  21. {
  22. title: '年龄',
  23. dataIndex: 'age',
  24. key: 'age',
  25. },
  26. {
  27. title: '住址',
  28. dataIndex: 'address',
  29. key: 'address',
  30. },
  31. ];
  32. <Table dataSource={dataSource} columns={columns} />;

代码演示

Table表格 - 图1

基本用法

简单的表格,最后一列是各种操作。

  1. import { Table, Tag, Space } from 'antd';
  2. const columns = [
  3. {
  4. title: 'Name',
  5. dataIndex: 'name',
  6. key: 'name',
  7. render: text => <a>{text}</a>,
  8. },
  9. {
  10. title: 'Age',
  11. dataIndex: 'age',
  12. key: 'age',
  13. },
  14. {
  15. title: 'Address',
  16. dataIndex: 'address',
  17. key: 'address',
  18. },
  19. {
  20. title: 'Tags',
  21. key: 'tags',
  22. dataIndex: 'tags',
  23. render: tags => (
  24. <>
  25. {tags.map(tag => {
  26. let color = tag.length > 5 ? 'geekblue' : 'green';
  27. if (tag === 'loser') {
  28. color = 'volcano';
  29. }
  30. return (
  31. <Tag color={color} key={tag}>
  32. {tag.toUpperCase()}
  33. </Tag>
  34. );
  35. })}
  36. </>
  37. ),
  38. },
  39. {
  40. title: 'Action',
  41. key: 'action',
  42. render: (text, record) => (
  43. <Space size="middle">
  44. <a>Invite {record.name}</a>
  45. <a>Delete</a>
  46. </Space>
  47. ),
  48. },
  49. ];
  50. const data = [
  51. {
  52. key: '1',
  53. name: 'John Brown',
  54. age: 32,
  55. address: 'New York No. 1 Lake Park',
  56. tags: ['nice', 'developer'],
  57. },
  58. {
  59. key: '2',
  60. name: 'Jim Green',
  61. age: 42,
  62. address: 'London No. 1 Lake Park',
  63. tags: ['loser'],
  64. },
  65. {
  66. key: '3',
  67. name: 'Joe Black',
  68. age: 32,
  69. address: 'Sidney No. 1 Lake Park',
  70. tags: ['cool', 'teacher'],
  71. },
  72. ];
  73. ReactDOM.render(<Table columns={columns} dataSource={data} />, mountNode);

Table表格 - 图2

JSX 风格的 API

使用 JSX 风格的 API(2.5.0 以后引入)

这个只是一个描述 columns 的语法糖,所以你不能用其他组件去包裹 ColumnColumnGroup

  1. import { Table, Tag, Space } from 'antd';
  2. const { Column, ColumnGroup } = Table;
  3. const data = [
  4. {
  5. key: '1',
  6. firstName: 'John',
  7. lastName: 'Brown',
  8. age: 32,
  9. address: 'New York No. 1 Lake Park',
  10. tags: ['nice', 'developer'],
  11. },
  12. {
  13. key: '2',
  14. firstName: 'Jim',
  15. lastName: 'Green',
  16. age: 42,
  17. address: 'London No. 1 Lake Park',
  18. tags: ['loser'],
  19. },
  20. {
  21. key: '3',
  22. firstName: 'Joe',
  23. lastName: 'Black',
  24. age: 32,
  25. address: 'Sidney No. 1 Lake Park',
  26. tags: ['cool', 'teacher'],
  27. },
  28. ];
  29. ReactDOM.render(
  30. <Table dataSource={data}>
  31. <ColumnGroup title="Name">
  32. <Column title="First Name" dataIndex="firstName" key="firstName" />
  33. <Column title="Last Name" dataIndex="lastName" key="lastName" />
  34. </ColumnGroup>
  35. <Column title="Age" dataIndex="age" key="age" />
  36. <Column title="Address" dataIndex="address" key="address" />
  37. <Column
  38. title="Tags"
  39. dataIndex="tags"
  40. key="tags"
  41. render={tags => (
  42. <>
  43. {tags.map(tag => (
  44. <Tag color="blue" key={tag}>
  45. {tag}
  46. </Tag>
  47. ))}
  48. </>
  49. )}
  50. />
  51. <Column
  52. title="Action"
  53. key="action"
  54. render={(text, record) => (
  55. <Space size="middle">
  56. <a>Invite {record.lastName}</a>
  57. <a>Delete</a>
  58. </Space>
  59. )}
  60. />
  61. </Table>,
  62. mountNode,
  63. );

Table表格 - 图3

可选择

第一列是联动的选择框。可以通过 rowSelection.type 属性指定选择类型,默认为 checkbox

默认点击 checkbox 触发选择行为,需要点击行触发可以参考例子:https://codesandbox.io/s/000vqw38rl

TypeScript

JavaScript

Table表格 - 图4

  1. import React, { useState } from 'react';
  2. import { Table, Radio, Divider } from 'antd';
  3. const columns = [
  4. {
  5. title: 'Name',
  6. dataIndex: 'name',
  7. render: (text: string) => <a>{text}</a>,
  8. },
  9. {
  10. title: 'Age',
  11. dataIndex: 'age',
  12. },
  13. {
  14. title: 'Address',
  15. dataIndex: 'address',
  16. },
  17. ];
  18. interface DataType {
  19. key: React.Key;
  20. name: string;
  21. age: number;
  22. address: string;
  23. }
  24. const data: DataType[] = [
  25. {
  26. key: '1',
  27. name: 'John Brown',
  28. age: 32,
  29. address: 'New York No. 1 Lake Park',
  30. },
  31. {
  32. key: '2',
  33. name: 'Jim Green',
  34. age: 42,
  35. address: 'London No. 1 Lake Park',
  36. },
  37. {
  38. key: '3',
  39. name: 'Joe Black',
  40. age: 32,
  41. address: 'Sidney No. 1 Lake Park',
  42. },
  43. {
  44. key: '4',
  45. name: 'Disabled User',
  46. age: 99,
  47. address: 'Sidney No. 1 Lake Park',
  48. },
  49. ];
  50. // rowSelection object indicates the need for row selection
  51. const rowSelection = {
  52. onChange: (selectedRowKeys: React.Key[], selectedRows: DataType[]) => {
  53. console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
  54. },
  55. getCheckboxProps: (record: DataType) => ({
  56. disabled: record.name === 'Disabled User', // Column configuration not to be checked
  57. name: record.name,
  58. }),
  59. };
  60. const Demo = () => {
  61. const [selectionType, setSelectionType] = useState<'checkbox' | 'radio'>('checkbox');
  62. return (
  63. <div>
  64. <Radio.Group
  65. onChange={({ target: { value } }) => {
  66. setSelectionType(value);
  67. }}
  68. value={selectionType}
  69. >
  70. <Radio value="checkbox">Checkbox</Radio>
  71. <Radio value="radio">radio</Radio>
  72. </Radio.Group>
  73. <Divider />
  74. <Table
  75. rowSelection={{
  76. type: selectionType,
  77. ...rowSelection,
  78. }}
  79. columns={columns}
  80. dataSource={data}
  81. />
  82. </div>
  83. );
  84. };
  85. ReactDOM.render(<Demo />, mountNode);

Table表格 - 图5

选择和操作

选择后进行操作,完成后清空选择,通过 rowSelection.selectedRowKeys 来控制选中项。

  1. import { Table, Button } from 'antd';
  2. const columns = [
  3. {
  4. title: 'Name',
  5. dataIndex: 'name',
  6. },
  7. {
  8. title: 'Age',
  9. dataIndex: 'age',
  10. },
  11. {
  12. title: 'Address',
  13. dataIndex: 'address',
  14. },
  15. ];
  16. const data = [];
  17. for (let i = 0; i < 46; i++) {
  18. data.push({
  19. key: i,
  20. name: `Edward King ${i}`,
  21. age: 32,
  22. address: `London, Park Lane no. ${i}`,
  23. });
  24. }
  25. class App extends React.Component {
  26. state = {
  27. selectedRowKeys: [], // Check here to configure the default column
  28. loading: false,
  29. };
  30. start = () => {
  31. this.setState({ loading: true });
  32. // ajax request after empty completing
  33. setTimeout(() => {
  34. this.setState({
  35. selectedRowKeys: [],
  36. loading: false,
  37. });
  38. }, 1000);
  39. };
  40. onSelectChange = selectedRowKeys => {
  41. console.log('selectedRowKeys changed: ', selectedRowKeys);
  42. this.setState({ selectedRowKeys });
  43. };
  44. render() {
  45. const { loading, selectedRowKeys } = this.state;
  46. const rowSelection = {
  47. selectedRowKeys,
  48. onChange: this.onSelectChange,
  49. };
  50. const hasSelected = selectedRowKeys.length > 0;
  51. return (
  52. <div>
  53. <div style={{ marginBottom: 16 }}>
  54. <Button type="primary" onClick={this.start} disabled={!hasSelected} loading={loading}>
  55. Reload
  56. </Button>
  57. <span style={{ marginLeft: 8 }}>
  58. {hasSelected ? `Selected ${selectedRowKeys.length} items` : ''}
  59. </span>
  60. </div>
  61. <Table rowSelection={rowSelection} columns={columns} dataSource={data} />
  62. </div>
  63. );
  64. }
  65. }
  66. ReactDOM.render(<App />, mountNode);

Table表格 - 图6

自定义选择项

通过 rowSelection.selections 自定义选择项,默认不显示下拉选项,设为 true 时显示默认选择项。

  1. import { Table } from 'antd';
  2. const columns = [
  3. {
  4. title: 'Name',
  5. dataIndex: 'name',
  6. },
  7. {
  8. title: 'Age',
  9. dataIndex: 'age',
  10. },
  11. {
  12. title: 'Address',
  13. dataIndex: 'address',
  14. },
  15. ];
  16. const data = [];
  17. for (let i = 0; i < 46; i++) {
  18. data.push({
  19. key: i,
  20. name: `Edward King ${i}`,
  21. age: 32,
  22. address: `London, Park Lane no. ${i}`,
  23. });
  24. }
  25. class App extends React.Component {
  26. state = {
  27. selectedRowKeys: [], // Check here to configure the default column
  28. };
  29. onSelectChange = selectedRowKeys => {
  30. console.log('selectedRowKeys changed: ', selectedRowKeys);
  31. this.setState({ selectedRowKeys });
  32. };
  33. render() {
  34. const { selectedRowKeys } = this.state;
  35. const rowSelection = {
  36. selectedRowKeys,
  37. onChange: this.onSelectChange,
  38. selections: [
  39. Table.SELECTION_ALL,
  40. Table.SELECTION_INVERT,
  41. Table.SELECTION_NONE,
  42. {
  43. key: 'odd',
  44. text: 'Select Odd Row',
  45. onSelect: changableRowKeys => {
  46. let newSelectedRowKeys = [];
  47. newSelectedRowKeys = changableRowKeys.filter((key, index) => {
  48. if (index % 2 !== 0) {
  49. return false;
  50. }
  51. return true;
  52. });
  53. this.setState({ selectedRowKeys: newSelectedRowKeys });
  54. },
  55. },
  56. {
  57. key: 'even',
  58. text: 'Select Even Row',
  59. onSelect: changableRowKeys => {
  60. let newSelectedRowKeys = [];
  61. newSelectedRowKeys = changableRowKeys.filter((key, index) => {
  62. if (index % 2 !== 0) {
  63. return true;
  64. }
  65. return false;
  66. });
  67. this.setState({ selectedRowKeys: newSelectedRowKeys });
  68. },
  69. },
  70. ],
  71. };
  72. return <Table rowSelection={rowSelection} columns={columns} dataSource={data} />;
  73. }
  74. }
  75. ReactDOM.render(<App />, mountNode);

Table表格 - 图7

筛选和排序

对某一列数据进行筛选,使用列的 filters 属性来指定需要筛选菜单的列,onFilter 用于筛选当前数据,filterMultiple 用于指定多选和单选。

对某一列数据进行排序,通过指定列的 sorter 函数即可启动排序按钮。sorter: function(rowA, rowB) { ... }, rowA、rowB 为比较的两个行数据。

sortDirections: ['ascend' | 'descend']改变每列可用的排序方式,切换排序时按数组内容依次切换,设置在 table props 上时对所有列生效。你可以通过设置 ['ascend', 'descend', 'ascend'] 禁止排序恢复到默认状态。

使用 defaultSortOrder 属性,设置列的默认排序顺序。

  1. import { Table } from 'antd';
  2. const columns = [
  3. {
  4. title: 'Name',
  5. dataIndex: 'name',
  6. filters: [
  7. {
  8. text: 'Joe',
  9. value: 'Joe',
  10. },
  11. {
  12. text: 'Jim',
  13. value: 'Jim',
  14. },
  15. {
  16. text: 'Submenu',
  17. value: 'Submenu',
  18. children: [
  19. {
  20. text: 'Green',
  21. value: 'Green',
  22. },
  23. {
  24. text: 'Black',
  25. value: 'Black',
  26. },
  27. ],
  28. },
  29. ],
  30. // specify the condition of filtering result
  31. // here is that finding the name started with `value`
  32. onFilter: (value, record) => record.name.indexOf(value) === 0,
  33. sorter: (a, b) => a.name.length - b.name.length,
  34. sortDirections: ['descend'],
  35. },
  36. {
  37. title: 'Age',
  38. dataIndex: 'age',
  39. defaultSortOrder: 'descend',
  40. sorter: (a, b) => a.age - b.age,
  41. },
  42. {
  43. title: 'Address',
  44. dataIndex: 'address',
  45. filters: [
  46. {
  47. text: 'London',
  48. value: 'London',
  49. },
  50. {
  51. text: 'New York',
  52. value: 'New York',
  53. },
  54. ],
  55. onFilter: (value, record) => record.address.indexOf(value) === 0,
  56. },
  57. ];
  58. const data = [
  59. {
  60. key: '1',
  61. name: 'John Brown',
  62. age: 32,
  63. address: 'New York No. 1 Lake Park',
  64. },
  65. {
  66. key: '2',
  67. name: 'Jim Green',
  68. age: 42,
  69. address: 'London No. 1 Lake Park',
  70. },
  71. {
  72. key: '3',
  73. name: 'Joe Black',
  74. age: 32,
  75. address: 'Sidney No. 1 Lake Park',
  76. },
  77. {
  78. key: '4',
  79. name: 'Jim Red',
  80. age: 32,
  81. address: 'London No. 2 Lake Park',
  82. },
  83. ];
  84. function onChange(pagination, filters, sorter, extra) {
  85. console.log('params', pagination, filters, sorter, extra);
  86. }
  87. ReactDOM.render(<Table columns={columns} dataSource={data} onChange={onChange} />, mountNode);

Table表格 - 图8

多列排序

column.sorter 支持 multiple 字段以配置多列排序优先级。通过 sorter.compare 配置排序逻辑,你可以通过不设置该函数只启动多列排序的交互形式。

  1. import { Table } from 'antd';
  2. const columns = [
  3. {
  4. title: 'Name',
  5. dataIndex: 'name',
  6. },
  7. {
  8. title: 'Chinese Score',
  9. dataIndex: 'chinese',
  10. sorter: {
  11. compare: (a, b) => a.chinese - b.chinese,
  12. multiple: 3,
  13. },
  14. },
  15. {
  16. title: 'Math Score',
  17. dataIndex: 'math',
  18. sorter: {
  19. compare: (a, b) => a.math - b.math,
  20. multiple: 2,
  21. },
  22. },
  23. {
  24. title: 'English Score',
  25. dataIndex: 'english',
  26. sorter: {
  27. compare: (a, b) => a.english - b.english,
  28. multiple: 1,
  29. },
  30. },
  31. ];
  32. const data = [
  33. {
  34. key: '1',
  35. name: 'John Brown',
  36. chinese: 98,
  37. math: 60,
  38. english: 70,
  39. },
  40. {
  41. key: '2',
  42. name: 'Jim Green',
  43. chinese: 98,
  44. math: 66,
  45. english: 89,
  46. },
  47. {
  48. key: '3',
  49. name: 'Joe Black',
  50. chinese: 98,
  51. math: 90,
  52. english: 70,
  53. },
  54. {
  55. key: '4',
  56. name: 'Jim Red',
  57. chinese: 88,
  58. math: 99,
  59. english: 89,
  60. },
  61. ];
  62. function onChange(pagination, filters, sorter, extra) {
  63. console.log('params', pagination, filters, sorter, extra);
  64. }
  65. ReactDOM.render(<Table columns={columns} dataSource={data} onChange={onChange} />, mountNode);

Table表格 - 图9

可控的筛选和排序

使用受控属性对筛选和排序状态进行控制。

  1. columns 中定义了 filteredValue 和 sortOrder 属性即视为受控模式。

  2. 只支持同时对一列进行排序,请保证只有一列的 sortOrder 属性是生效的。

  3. 务必指定 column.key

  1. import { Table, Button, Space } from 'antd';
  2. const data = [
  3. {
  4. key: '1',
  5. name: 'John Brown',
  6. age: 32,
  7. address: 'New York No. 1 Lake Park',
  8. },
  9. {
  10. key: '2',
  11. name: 'Jim Green',
  12. age: 42,
  13. address: 'London No. 1 Lake Park',
  14. },
  15. {
  16. key: '3',
  17. name: 'Joe Black',
  18. age: 32,
  19. address: 'Sidney No. 1 Lake Park',
  20. },
  21. {
  22. key: '4',
  23. name: 'Jim Red',
  24. age: 32,
  25. address: 'London No. 2 Lake Park',
  26. },
  27. ];
  28. class App extends React.Component {
  29. state = {
  30. filteredInfo: null,
  31. sortedInfo: null,
  32. };
  33. handleChange = (pagination, filters, sorter) => {
  34. console.log('Various parameters', pagination, filters, sorter);
  35. this.setState({
  36. filteredInfo: filters,
  37. sortedInfo: sorter,
  38. });
  39. };
  40. clearFilters = () => {
  41. this.setState({ filteredInfo: null });
  42. };
  43. clearAll = () => {
  44. this.setState({
  45. filteredInfo: null,
  46. sortedInfo: null,
  47. });
  48. };
  49. setAgeSort = () => {
  50. this.setState({
  51. sortedInfo: {
  52. order: 'descend',
  53. columnKey: 'age',
  54. },
  55. });
  56. };
  57. render() {
  58. let { sortedInfo, filteredInfo } = this.state;
  59. sortedInfo = sortedInfo || {};
  60. filteredInfo = filteredInfo || {};
  61. const columns = [
  62. {
  63. title: 'Name',
  64. dataIndex: 'name',
  65. key: 'name',
  66. filters: [
  67. { text: 'Joe', value: 'Joe' },
  68. { text: 'Jim', value: 'Jim' },
  69. ],
  70. filteredValue: filteredInfo.name || null,
  71. onFilter: (value, record) => record.name.includes(value),
  72. sorter: (a, b) => a.name.length - b.name.length,
  73. sortOrder: sortedInfo.columnKey === 'name' && sortedInfo.order,
  74. ellipsis: true,
  75. },
  76. {
  77. title: 'Age',
  78. dataIndex: 'age',
  79. key: 'age',
  80. sorter: (a, b) => a.age - b.age,
  81. sortOrder: sortedInfo.columnKey === 'age' && sortedInfo.order,
  82. ellipsis: true,
  83. },
  84. {
  85. title: 'Address',
  86. dataIndex: 'address',
  87. key: 'address',
  88. filters: [
  89. { text: 'London', value: 'London' },
  90. { text: 'New York', value: 'New York' },
  91. ],
  92. filteredValue: filteredInfo.address || null,
  93. onFilter: (value, record) => record.address.includes(value),
  94. sorter: (a, b) => a.address.length - b.address.length,
  95. sortOrder: sortedInfo.columnKey === 'address' && sortedInfo.order,
  96. ellipsis: true,
  97. },
  98. ];
  99. return (
  100. <>
  101. <Space style={{ marginBottom: 16 }}>
  102. <Button onClick={this.setAgeSort}>Sort age</Button>
  103. <Button onClick={this.clearFilters}>Clear filters</Button>
  104. <Button onClick={this.clearAll}>Clear filters and sorters</Button>
  105. </Space>
  106. <Table columns={columns} dataSource={data} onChange={this.handleChange} />
  107. </>
  108. );
  109. }
  110. }
  111. ReactDOM.render(<App />, mountNode);

Table表格 - 图10

自定义筛选菜单

通过 filterDropdown 自定义的列筛选功能,并实现一个搜索列的示例。

给函数 confirm 添加 boolean 类型参数 closeDropdown,是否关闭筛选菜单,默认为 true

  1. import { Table, Input, Button, Space } from 'antd';
  2. import Highlighter from 'react-highlight-words';
  3. import { SearchOutlined } from '@ant-design/icons';
  4. const data = [
  5. {
  6. key: '1',
  7. name: 'John Brown',
  8. age: 32,
  9. address: 'New York No. 1 Lake Park',
  10. },
  11. {
  12. key: '2',
  13. name: 'Joe Black',
  14. age: 42,
  15. address: 'London No. 1 Lake Park',
  16. },
  17. {
  18. key: '3',
  19. name: 'Jim Green',
  20. age: 32,
  21. address: 'Sidney No. 1 Lake Park',
  22. },
  23. {
  24. key: '4',
  25. name: 'Jim Red',
  26. age: 32,
  27. address: 'London No. 2 Lake Park',
  28. },
  29. ];
  30. class App extends React.Component {
  31. state = {
  32. searchText: '',
  33. searchedColumn: '',
  34. };
  35. getColumnSearchProps = dataIndex => ({
  36. filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
  37. <div style={{ padding: 8 }}>
  38. <Input
  39. ref={node => {
  40. this.searchInput = node;
  41. }}
  42. placeholder={`Search ${dataIndex}`}
  43. value={selectedKeys[0]}
  44. onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
  45. onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
  46. style={{ marginBottom: 8, display: 'block' }}
  47. />
  48. <Space>
  49. <Button
  50. type="primary"
  51. onClick={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
  52. icon={<SearchOutlined />}
  53. size="small"
  54. style={{ width: 90 }}
  55. >
  56. Search
  57. </Button>
  58. <Button onClick={() => this.handleReset(clearFilters)} size="small" style={{ width: 90 }}>
  59. Reset
  60. </Button>
  61. <Button
  62. type="link"
  63. size="small"
  64. onClick={() => {
  65. confirm({ closeDropdown: false });
  66. this.setState({
  67. searchText: selectedKeys[0],
  68. searchedColumn: dataIndex,
  69. });
  70. }}
  71. >
  72. Filter
  73. </Button>
  74. </Space>
  75. </div>
  76. ),
  77. filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
  78. onFilter: (value, record) =>
  79. record[dataIndex]
  80. ? record[dataIndex].toString().toLowerCase().includes(value.toLowerCase())
  81. : '',
  82. onFilterDropdownVisibleChange: visible => {
  83. if (visible) {
  84. setTimeout(() => this.searchInput.select(), 100);
  85. }
  86. },
  87. render: text =>
  88. this.state.searchedColumn === dataIndex ? (
  89. <Highlighter
  90. highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
  91. searchWords={[this.state.searchText]}
  92. autoEscape
  93. textToHighlight={text ? text.toString() : ''}
  94. />
  95. ) : (
  96. text
  97. ),
  98. });
  99. handleSearch = (selectedKeys, confirm, dataIndex) => {
  100. confirm();
  101. this.setState({
  102. searchText: selectedKeys[0],
  103. searchedColumn: dataIndex,
  104. });
  105. };
  106. handleReset = clearFilters => {
  107. clearFilters();
  108. this.setState({ searchText: '' });
  109. };
  110. render() {
  111. const columns = [
  112. {
  113. title: 'Name',
  114. dataIndex: 'name',
  115. key: 'name',
  116. width: '30%',
  117. ...this.getColumnSearchProps('name'),
  118. },
  119. {
  120. title: 'Age',
  121. dataIndex: 'age',
  122. key: 'age',
  123. width: '20%',
  124. ...this.getColumnSearchProps('age'),
  125. },
  126. {
  127. title: 'Address',
  128. dataIndex: 'address',
  129. key: 'address',
  130. ...this.getColumnSearchProps('address'),
  131. sorter: (a, b) => a.address.length - b.address.length,
  132. sortDirections: ['descend', 'ascend'],
  133. },
  134. ];
  135. return <Table columns={columns} dataSource={data} />;
  136. }
  137. }
  138. ReactDOM.render(<App />, mountNode);

Table表格 - 图11

远程加载数据

这个例子通过简单的 ajax 读取方式,演示了如何从服务端读取并展现数据,具有筛选、排序等功能以及页面 loading 效果。开发者可以自行接入其他数据处理方式。

另外,本例也展示了筛选排序功能如何交给服务端实现,列不需要指定具体的 onFiltersorter 函数,而是在把筛选和排序的参数发到服务端来处理。

当使用 rowSelection 时,请设置 rowSelection.preserveSelectedRowKeys 属性以保留 key

注意,此示例使用 模拟接口,展示数据可能不准确,请打开网络面板查看请求。

🛎️ 想要 3 分钟实现?试试 ProTable

  1. import { Table } from 'antd';
  2. import reqwest from 'reqwest';
  3. const columns = [
  4. {
  5. title: 'Name',
  6. dataIndex: 'name',
  7. sorter: true,
  8. render: name => `${name.first} ${name.last}`,
  9. width: '20%',
  10. },
  11. {
  12. title: 'Gender',
  13. dataIndex: 'gender',
  14. filters: [
  15. { text: 'Male', value: 'male' },
  16. { text: 'Female', value: 'female' },
  17. ],
  18. width: '20%',
  19. },
  20. {
  21. title: 'Email',
  22. dataIndex: 'email',
  23. },
  24. ];
  25. const getRandomuserParams = params => ({
  26. results: params.pagination.pageSize,
  27. page: params.pagination.current,
  28. ...params,
  29. });
  30. class App extends React.Component {
  31. state = {
  32. data: [],
  33. pagination: {
  34. current: 1,
  35. pageSize: 10,
  36. },
  37. loading: false,
  38. };
  39. componentDidMount() {
  40. const { pagination } = this.state;
  41. this.fetch({ pagination });
  42. }
  43. handleTableChange = (pagination, filters, sorter) => {
  44. this.fetch({
  45. sortField: sorter.field,
  46. sortOrder: sorter.order,
  47. pagination,
  48. ...filters,
  49. });
  50. };
  51. fetch = (params = {}) => {
  52. this.setState({ loading: true });
  53. reqwest({
  54. url: 'https://randomuser.me/api',
  55. method: 'get',
  56. type: 'json',
  57. data: getRandomuserParams(params),
  58. }).then(data => {
  59. console.log(data);
  60. this.setState({
  61. loading: false,
  62. data: data.results,
  63. pagination: {
  64. ...params.pagination,
  65. total: 200,
  66. // 200 is mock data, you should read it from server
  67. // total: data.totalCount,
  68. },
  69. });
  70. });
  71. };
  72. render() {
  73. const { data, pagination, loading } = this.state;
  74. return (
  75. <Table
  76. columns={columns}
  77. rowKey={record => record.login.uuid}
  78. dataSource={data}
  79. pagination={pagination}
  80. loading={loading}
  81. onChange={this.handleTableChange}
  82. />
  83. );
  84. }
  85. }
  86. ReactDOM.render(<App />, mountNode);

Table表格 - 图12

紧凑型

两种紧凑型的列表,小型列表只用于对话框内。

  1. import { Table } from 'antd';
  2. const columns = [
  3. {
  4. title: 'Name',
  5. dataIndex: 'name',
  6. },
  7. {
  8. title: 'Age',
  9. dataIndex: 'age',
  10. },
  11. {
  12. title: 'Address',
  13. dataIndex: 'address',
  14. },
  15. ];
  16. const data = [
  17. {
  18. key: '1',
  19. name: 'John Brown',
  20. age: 32,
  21. address: 'New York No. 1 Lake Park',
  22. },
  23. {
  24. key: '2',
  25. name: 'Jim Green',
  26. age: 42,
  27. address: 'London No. 1 Lake Park',
  28. },
  29. {
  30. key: '3',
  31. name: 'Joe Black',
  32. age: 32,
  33. address: 'Sidney No. 1 Lake Park',
  34. },
  35. ];
  36. ReactDOM.render(
  37. <div>
  38. <h4>Middle size table</h4>
  39. <Table columns={columns} dataSource={data} size="middle" />
  40. <h4>Small size table</h4>
  41. <Table columns={columns} dataSource={data} size="small" />
  42. </div>,
  43. mountNode,
  44. );

Table表格 - 图13

带边框

添加表格边框线,页头和页脚。

  1. import { Table } from 'antd';
  2. const columns = [
  3. {
  4. title: 'Name',
  5. dataIndex: 'name',
  6. render: text => <a>{text}</a>,
  7. },
  8. {
  9. title: 'Cash Assets',
  10. className: 'column-money',
  11. dataIndex: 'money',
  12. align: 'right',
  13. },
  14. {
  15. title: 'Address',
  16. dataIndex: 'address',
  17. },
  18. ];
  19. const data = [
  20. {
  21. key: '1',
  22. name: 'John Brown',
  23. money: '¥300,000.00',
  24. address: 'New York No. 1 Lake Park',
  25. },
  26. {
  27. key: '2',
  28. name: 'Jim Green',
  29. money: '¥1,256,000.00',
  30. address: 'London No. 1 Lake Park',
  31. },
  32. {
  33. key: '3',
  34. name: 'Joe Black',
  35. money: '¥120,000.00',
  36. address: 'Sidney No. 1 Lake Park',
  37. },
  38. ];
  39. ReactDOM.render(
  40. <Table
  41. columns={columns}
  42. dataSource={data}
  43. bordered
  44. title={() => 'Header'}
  45. footer={() => 'Footer'}
  46. />,
  47. mountNode,
  48. );

Table表格 - 图14

可展开

当表格内容较多不能一次性完全展示时。

  1. import { Table } from 'antd';
  2. const columns = [
  3. { title: 'Name', dataIndex: 'name', key: 'name' },
  4. { title: 'Age', dataIndex: 'age', key: 'age' },
  5. { title: 'Address', dataIndex: 'address', key: 'address' },
  6. {
  7. title: 'Action',
  8. dataIndex: '',
  9. key: 'x',
  10. render: () => <a>Delete</a>,
  11. },
  12. ];
  13. const data = [
  14. {
  15. key: 1,
  16. name: 'John Brown',
  17. age: 32,
  18. address: 'New York No. 1 Lake Park',
  19. description: 'My name is John Brown, I am 32 years old, living in New York No. 1 Lake Park.',
  20. },
  21. {
  22. key: 2,
  23. name: 'Jim Green',
  24. age: 42,
  25. address: 'London No. 1 Lake Park',
  26. description: 'My name is Jim Green, I am 42 years old, living in London No. 1 Lake Park.',
  27. },
  28. {
  29. key: 3,
  30. name: 'Not Expandable',
  31. age: 29,
  32. address: 'Jiangsu No. 1 Lake Park',
  33. description: 'This not expandable',
  34. },
  35. {
  36. key: 4,
  37. name: 'Joe Black',
  38. age: 32,
  39. address: 'Sidney No. 1 Lake Park',
  40. description: 'My name is Joe Black, I am 32 years old, living in Sidney No. 1 Lake Park.',
  41. },
  42. ];
  43. ReactDOM.render(
  44. <Table
  45. columns={columns}
  46. expandable={{
  47. expandedRowRender: record => <p style={{ margin: 0 }}>{record.description}</p>,
  48. rowExpandable: record => record.name !== 'Not Expandable',
  49. }}
  50. dataSource={data}
  51. />,
  52. mountNode,
  53. );

Table表格 - 图15

表格行/列合并

表头只支持列合并,使用 column 里的 colSpan 进行设置。

表格支持行/列合并,使用 render 里的单元格属性 colSpan 或者 rowSpan 设值为 0 时,设置的表格不会渲染。

  1. import { Table } from 'antd';
  2. // In the fifth row, other columns are merged into first column
  3. // by setting it's colSpan to be 0
  4. const renderContent = (value, row, index) => {
  5. const obj = {
  6. children: value,
  7. props: {},
  8. };
  9. if (index === 4) {
  10. obj.props.colSpan = 0;
  11. }
  12. return obj;
  13. };
  14. const columns = [
  15. {
  16. title: 'Name',
  17. dataIndex: 'name',
  18. render: (text, row, index) => {
  19. if (index < 4) {
  20. return <a>{text}</a>;
  21. }
  22. return {
  23. children: <a>{text}</a>,
  24. props: {
  25. colSpan: 5,
  26. },
  27. };
  28. },
  29. },
  30. {
  31. title: 'Age',
  32. dataIndex: 'age',
  33. render: renderContent,
  34. },
  35. {
  36. title: 'Home phone',
  37. colSpan: 2,
  38. dataIndex: 'tel',
  39. render: (value, row, index) => {
  40. const obj = {
  41. children: value,
  42. props: {},
  43. };
  44. if (index === 2) {
  45. obj.props.rowSpan = 2;
  46. }
  47. // These two are merged into above cell
  48. if (index === 3) {
  49. obj.props.rowSpan = 0;
  50. }
  51. if (index === 4) {
  52. obj.props.colSpan = 0;
  53. }
  54. return obj;
  55. },
  56. },
  57. {
  58. title: 'Phone',
  59. colSpan: 0,
  60. dataIndex: 'phone',
  61. render: renderContent,
  62. },
  63. {
  64. title: 'Address',
  65. dataIndex: 'address',
  66. render: renderContent,
  67. },
  68. ];
  69. const data = [
  70. {
  71. key: '1',
  72. name: 'John Brown',
  73. age: 32,
  74. tel: '0571-22098909',
  75. phone: 18889898989,
  76. address: 'New York No. 1 Lake Park',
  77. },
  78. {
  79. key: '2',
  80. name: 'Jim Green',
  81. tel: '0571-22098333',
  82. phone: 18889898888,
  83. age: 42,
  84. address: 'London No. 1 Lake Park',
  85. },
  86. {
  87. key: '3',
  88. name: 'Joe Black',
  89. age: 32,
  90. tel: '0575-22098909',
  91. phone: 18900010002,
  92. address: 'Sidney No. 1 Lake Park',
  93. },
  94. {
  95. key: '4',
  96. name: 'Jim Red',
  97. age: 18,
  98. tel: '0575-22098909',
  99. phone: 18900010002,
  100. address: 'London No. 2 Lake Park',
  101. },
  102. {
  103. key: '5',
  104. name: 'Jake White',
  105. age: 18,
  106. tel: '0575-22098909',
  107. phone: 18900010002,
  108. address: 'Dublin No. 2 Lake Park',
  109. },
  110. ];
  111. ReactDOM.render(<Table columns={columns} dataSource={data} bordered />, mountNode);

Table表格 - 图16

树形数据展示

表格支持树形数据的展示,当数据中有 children 字段时会自动展示为树形表格,如果不需要或配置为其他字段可以用 childrenColumnName 进行配置。

可以通过设置 indentSize 以控制每一层的缩进宽度。

  1. import { Table, Switch, Space } from 'antd';
  2. const columns = [
  3. {
  4. title: 'Name',
  5. dataIndex: 'name',
  6. key: 'name',
  7. },
  8. {
  9. title: 'Age',
  10. dataIndex: 'age',
  11. key: 'age',
  12. width: '12%',
  13. },
  14. {
  15. title: 'Address',
  16. dataIndex: 'address',
  17. width: '30%',
  18. key: 'address',
  19. },
  20. ];
  21. const data = [
  22. {
  23. key: 1,
  24. name: 'John Brown sr.',
  25. age: 60,
  26. address: 'New York No. 1 Lake Park',
  27. children: [
  28. {
  29. key: 11,
  30. name: 'John Brown',
  31. age: 42,
  32. address: 'New York No. 2 Lake Park',
  33. },
  34. {
  35. key: 12,
  36. name: 'John Brown jr.',
  37. age: 30,
  38. address: 'New York No. 3 Lake Park',
  39. children: [
  40. {
  41. key: 121,
  42. name: 'Jimmy Brown',
  43. age: 16,
  44. address: 'New York No. 3 Lake Park',
  45. },
  46. ],
  47. },
  48. {
  49. key: 13,
  50. name: 'Jim Green sr.',
  51. age: 72,
  52. address: 'London No. 1 Lake Park',
  53. children: [
  54. {
  55. key: 131,
  56. name: 'Jim Green',
  57. age: 42,
  58. address: 'London No. 2 Lake Park',
  59. children: [
  60. {
  61. key: 1311,
  62. name: 'Jim Green jr.',
  63. age: 25,
  64. address: 'London No. 3 Lake Park',
  65. },
  66. {
  67. key: 1312,
  68. name: 'Jimmy Green sr.',
  69. age: 18,
  70. address: 'London No. 4 Lake Park',
  71. },
  72. ],
  73. },
  74. ],
  75. },
  76. ],
  77. },
  78. {
  79. key: 2,
  80. name: 'Joe Black',
  81. age: 32,
  82. address: 'Sidney No. 1 Lake Park',
  83. },
  84. ];
  85. // rowSelection objects indicates the need for row selection
  86. const rowSelection = {
  87. onChange: (selectedRowKeys, selectedRows) => {
  88. console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
  89. },
  90. onSelect: (record, selected, selectedRows) => {
  91. console.log(record, selected, selectedRows);
  92. },
  93. onSelectAll: (selected, selectedRows, changeRows) => {
  94. console.log(selected, selectedRows, changeRows);
  95. },
  96. };
  97. function TreeData() {
  98. const [checkStrictly, setCheckStrictly] = React.useState(false);
  99. return (
  100. <>
  101. <Space align="center" style={{ marginBottom: 16 }}>
  102. CheckStrictly: <Switch checked={checkStrictly} onChange={setCheckStrictly} />
  103. </Space>
  104. <Table
  105. columns={columns}
  106. rowSelection={{ ...rowSelection, checkStrictly }}
  107. dataSource={data}
  108. />
  109. </>
  110. );
  111. }
  112. ReactDOM.render(<TreeData />, mountNode);

Table表格 - 图17

固定表头

方便一页内展示大量数据。

需要指定 column 的 width 属性,否则列头和内容可能不对齐。如果指定 width 不生效或出现白色垂直空隙,请尝试建议留一列不设宽度以适应弹性布局,或者检查是否有超长连续字段破坏布局

  1. import { Table } from 'antd';
  2. const columns = [
  3. {
  4. title: 'Name',
  5. dataIndex: 'name',
  6. width: 150,
  7. },
  8. {
  9. title: 'Age',
  10. dataIndex: 'age',
  11. width: 150,
  12. },
  13. {
  14. title: 'Address',
  15. dataIndex: 'address',
  16. },
  17. ];
  18. const data = [];
  19. for (let i = 0; i < 100; i++) {
  20. data.push({
  21. key: i,
  22. name: `Edward King ${i}`,
  23. age: 32,
  24. address: `London, Park Lane no. ${i}`,
  25. });
  26. }
  27. ReactDOM.render(
  28. <Table columns={columns} dataSource={data} pagination={{ pageSize: 50 }} scroll={{ y: 240 }} />,
  29. mountNode,
  30. );

Table表格 - 图18

固定列

对于列数很多的数据,可以固定前后的列,横向滚动查看其它数据,需要和 scroll.x 配合使用。

若列头与内容不对齐或出现列重复,请指定固定列的宽度 width。如果指定 width 不生效或出现白色垂直空隙,请尝试建议留一列不设宽度以适应弹性布局,或者检查是否有超长连续字段破坏布局

建议指定 scroll.x 为大于表格宽度的固定值或百分比。注意,且非固定列宽度之和不要超过 scroll.x

注意:v4 版本固定列通过 sticky 实现,IE 11 会降级成横向滚动。

  1. import { Table } from 'antd';
  2. const columns = [
  3. {
  4. title: 'Full Name',
  5. width: 100,
  6. dataIndex: 'name',
  7. key: 'name',
  8. fixed: 'left',
  9. },
  10. {
  11. title: 'Age',
  12. width: 100,
  13. dataIndex: 'age',
  14. key: 'age',
  15. fixed: 'left',
  16. },
  17. { title: 'Column 1', dataIndex: 'address', key: '1' },
  18. { title: 'Column 2', dataIndex: 'address', key: '2' },
  19. { title: 'Column 3', dataIndex: 'address', key: '3' },
  20. { title: 'Column 4', dataIndex: 'address', key: '4' },
  21. { title: 'Column 5', dataIndex: 'address', key: '5' },
  22. { title: 'Column 6', dataIndex: 'address', key: '6' },
  23. { title: 'Column 7', dataIndex: 'address', key: '7' },
  24. { title: 'Column 8', dataIndex: 'address', key: '8' },
  25. {
  26. title: 'Action',
  27. key: 'operation',
  28. fixed: 'right',
  29. width: 100,
  30. render: () => <a>action</a>,
  31. },
  32. ];
  33. const data = [
  34. {
  35. key: '1',
  36. name: 'John Brown',
  37. age: 32,
  38. address: 'New York Park',
  39. },
  40. {
  41. key: '2',
  42. name: 'Jim Green',
  43. age: 40,
  44. address: 'London Park',
  45. },
  46. ];
  47. ReactDOM.render(<Table columns={columns} dataSource={data} scroll={{ x: 1300 }} />, mountNode);

Table表格 - 图19

固定头和列

适合同时展示有大量数据和数据列。

若列头与内容不对齐或出现列重复,请指定固定列的宽度 width。如果指定 width 不生效或出现白色垂直空隙,请尝试建议留一列不设宽度以适应弹性布局,或者检查是否有超长连续字段破坏布局

建议指定 scroll.x 为大于表格宽度的固定值或百分比。注意,且非固定列宽度之和不要超过 scroll.x

  1. import { Table } from 'antd';
  2. const columns = [
  3. {
  4. title: 'Full Name',
  5. width: 100,
  6. dataIndex: 'name',
  7. key: 'name',
  8. fixed: 'left',
  9. },
  10. {
  11. title: 'Age',
  12. width: 100,
  13. dataIndex: 'age',
  14. key: 'age',
  15. fixed: 'left',
  16. },
  17. {
  18. title: 'Column 1',
  19. dataIndex: 'address',
  20. key: '1',
  21. width: 150,
  22. },
  23. {
  24. title: 'Column 2',
  25. dataIndex: 'address',
  26. key: '2',
  27. width: 150,
  28. },
  29. {
  30. title: 'Column 3',
  31. dataIndex: 'address',
  32. key: '3',
  33. width: 150,
  34. },
  35. {
  36. title: 'Column 4',
  37. dataIndex: 'address',
  38. key: '4',
  39. width: 150,
  40. },
  41. {
  42. title: 'Column 5',
  43. dataIndex: 'address',
  44. key: '5',
  45. width: 150,
  46. },
  47. {
  48. title: 'Column 6',
  49. dataIndex: 'address',
  50. key: '6',
  51. width: 150,
  52. },
  53. {
  54. title: 'Column 7',
  55. dataIndex: 'address',
  56. key: '7',
  57. width: 150,
  58. },
  59. { title: 'Column 8', dataIndex: 'address', key: '8' },
  60. {
  61. title: 'Action',
  62. key: 'operation',
  63. fixed: 'right',
  64. width: 100,
  65. render: () => <a>action</a>,
  66. },
  67. ];
  68. const data = [];
  69. for (let i = 0; i < 100; i++) {
  70. data.push({
  71. key: i,
  72. name: `Edrward ${i}`,
  73. age: 32,
  74. address: `London Park no. ${i}`,
  75. });
  76. }
  77. ReactDOM.render(
  78. <Table columns={columns} dataSource={data} scroll={{ x: 1500, y: 300 }} />,
  79. mountNode,
  80. );

Table表格 - 图20

表头分组

columns[n] 可以内嵌 children,以渲染分组表头。

  1. import { Table } from 'antd';
  2. const columns = [
  3. {
  4. title: 'Name',
  5. dataIndex: 'name',
  6. key: 'name',
  7. width: 100,
  8. fixed: 'left',
  9. filters: [
  10. {
  11. text: 'Joe',
  12. value: 'Joe',
  13. },
  14. {
  15. text: 'John',
  16. value: 'John',
  17. },
  18. ],
  19. onFilter: (value, record) => record.name.indexOf(value) === 0,
  20. },
  21. {
  22. title: 'Other',
  23. children: [
  24. {
  25. title: 'Age',
  26. dataIndex: 'age',
  27. key: 'age',
  28. width: 150,
  29. sorter: (a, b) => a.age - b.age,
  30. },
  31. {
  32. title: 'Address',
  33. children: [
  34. {
  35. title: 'Street',
  36. dataIndex: 'street',
  37. key: 'street',
  38. width: 150,
  39. },
  40. {
  41. title: 'Block',
  42. children: [
  43. {
  44. title: 'Building',
  45. dataIndex: 'building',
  46. key: 'building',
  47. width: 100,
  48. },
  49. {
  50. title: 'Door No.',
  51. dataIndex: 'number',
  52. key: 'number',
  53. width: 100,
  54. },
  55. ],
  56. },
  57. ],
  58. },
  59. ],
  60. },
  61. {
  62. title: 'Company',
  63. children: [
  64. {
  65. title: 'Company Address',
  66. dataIndex: 'companyAddress',
  67. key: 'companyAddress',
  68. width: 200,
  69. },
  70. {
  71. title: 'Company Name',
  72. dataIndex: 'companyName',
  73. key: 'companyName',
  74. },
  75. ],
  76. },
  77. {
  78. title: 'Gender',
  79. dataIndex: 'gender',
  80. key: 'gender',
  81. width: 80,
  82. fixed: 'right',
  83. },
  84. ];
  85. const data = [];
  86. for (let i = 0; i < 100; i++) {
  87. data.push({
  88. key: i,
  89. name: 'John Brown',
  90. age: i + 1,
  91. street: 'Lake Park',
  92. building: 'C',
  93. number: 2035,
  94. companyAddress: 'Lake Street 42',
  95. companyName: 'SoftLake Co',
  96. gender: 'M',
  97. });
  98. }
  99. ReactDOM.render(
  100. <Table
  101. columns={columns}
  102. dataSource={data}
  103. bordered
  104. size="middle"
  105. scroll={{ x: 'calc(700px + 50%)', y: 240 }}
  106. />,
  107. mountNode,
  108. );

Table表格 - 图21

可编辑单元格

带单元格编辑功能的表格。当配合 shouldCellUpdate 使用时请注意闭包问题

TypeScript

JavaScript

Table表格 - 图22

  1. import React, { useContext, useState, useEffect, useRef } from 'react';
  2. import { Table, Input, Button, Popconfirm, Form } from 'antd';
  3. import { FormInstance } from 'antd/lib/form';
  4. const EditableContext = React.createContext<FormInstance<any> | null>(null);
  5. interface Item {
  6. key: string;
  7. name: string;
  8. age: string;
  9. address: string;
  10. }
  11. interface EditableRowProps {
  12. index: number;
  13. }
  14. const EditableRow: React.FC<EditableRowProps> = ({ index, ...props }) => {
  15. const [form] = Form.useForm();
  16. return (
  17. <Form form={form} component={false}>
  18. <EditableContext.Provider value={form}>
  19. <tr {...props} />
  20. </EditableContext.Provider>
  21. </Form>
  22. );
  23. };
  24. interface EditableCellProps {
  25. title: React.ReactNode;
  26. editable: boolean;
  27. children: React.ReactNode;
  28. dataIndex: keyof Item;
  29. record: Item;
  30. handleSave: (record: Item) => void;
  31. }
  32. const EditableCell: React.FC<EditableCellProps> = ({
  33. title,
  34. editable,
  35. children,
  36. dataIndex,
  37. record,
  38. handleSave,
  39. ...restProps
  40. }) => {
  41. const [editing, setEditing] = useState(false);
  42. const inputRef = useRef<Input>(null);
  43. const form = useContext(EditableContext)!;
  44. useEffect(() => {
  45. if (editing) {
  46. inputRef.current!.focus();
  47. }
  48. }, [editing]);
  49. const toggleEdit = () => {
  50. setEditing(!editing);
  51. form.setFieldsValue({ [dataIndex]: record[dataIndex] });
  52. };
  53. const save = async () => {
  54. try {
  55. const values = await form.validateFields();
  56. toggleEdit();
  57. handleSave({ ...record, ...values });
  58. } catch (errInfo) {
  59. console.log('Save failed:', errInfo);
  60. }
  61. };
  62. let childNode = children;
  63. if (editable) {
  64. childNode = editing ? (
  65. <Form.Item
  66. style={{ margin: 0 }}
  67. name={dataIndex}
  68. rules={[
  69. {
  70. required: true,
  71. message: `${title} is required.`,
  72. },
  73. ]}
  74. >
  75. <Input ref={inputRef} onPressEnter={save} onBlur={save} />
  76. </Form.Item>
  77. ) : (
  78. <div className="editable-cell-value-wrap" style={{ paddingRight: 24 }} onClick={toggleEdit}>
  79. {children}
  80. </div>
  81. );
  82. }
  83. return <td {...restProps}>{childNode}</td>;
  84. };
  85. type EditableTableProps = Parameters<typeof Table>[0];
  86. interface DataType {
  87. key: React.Key;
  88. name: string;
  89. age: string;
  90. address: string;
  91. }
  92. interface EditableTableState {
  93. dataSource: DataType[];
  94. count: number;
  95. }
  96. type ColumnTypes = Exclude<EditableTableProps['columns'], undefined>;
  97. class EditableTable extends React.Component<EditableTableProps, EditableTableState> {
  98. columns: (ColumnTypes[number] & { editable?: boolean; dataIndex: string })[];
  99. constructor(props: EditableTableProps) {
  100. super(props);
  101. this.columns = [
  102. {
  103. title: 'name',
  104. dataIndex: 'name',
  105. width: '30%',
  106. editable: true,
  107. },
  108. {
  109. title: 'age',
  110. dataIndex: 'age',
  111. },
  112. {
  113. title: 'address',
  114. dataIndex: 'address',
  115. },
  116. {
  117. title: 'operation',
  118. dataIndex: 'operation',
  119. render: (_, record: { key: React.Key }) =>
  120. this.state.dataSource.length >= 1 ? (
  121. <Popconfirm title="Sure to delete?" onConfirm={() => this.handleDelete(record.key)}>
  122. <a>Delete</a>
  123. </Popconfirm>
  124. ) : null,
  125. },
  126. ];
  127. this.state = {
  128. dataSource: [
  129. {
  130. key: '0',
  131. name: 'Edward King 0',
  132. age: '32',
  133. address: 'London, Park Lane no. 0',
  134. },
  135. {
  136. key: '1',
  137. name: 'Edward King 1',
  138. age: '32',
  139. address: 'London, Park Lane no. 1',
  140. },
  141. ],
  142. count: 2,
  143. };
  144. }
  145. handleDelete = (key: React.Key) => {
  146. const dataSource = [...this.state.dataSource];
  147. this.setState({ dataSource: dataSource.filter(item => item.key !== key) });
  148. };
  149. handleAdd = () => {
  150. const { count, dataSource } = this.state;
  151. const newData: DataType = {
  152. key: count,
  153. name: `Edward King ${count}`,
  154. age: '32',
  155. address: `London, Park Lane no. ${count}`,
  156. };
  157. this.setState({
  158. dataSource: [...dataSource, newData],
  159. count: count + 1,
  160. });
  161. };
  162. handleSave = (row: DataType) => {
  163. const newData = [...this.state.dataSource];
  164. const index = newData.findIndex(item => row.key === item.key);
  165. const item = newData[index];
  166. newData.splice(index, 1, {
  167. ...item,
  168. ...row,
  169. });
  170. this.setState({ dataSource: newData });
  171. };
  172. render() {
  173. const { dataSource } = this.state;
  174. const components = {
  175. body: {
  176. row: EditableRow,
  177. cell: EditableCell,
  178. },
  179. };
  180. const columns = this.columns.map(col => {
  181. if (!col.editable) {
  182. return col;
  183. }
  184. return {
  185. ...col,
  186. onCell: (record: DataType) => ({
  187. record,
  188. editable: col.editable,
  189. dataIndex: col.dataIndex,
  190. title: col.title,
  191. handleSave: this.handleSave,
  192. }),
  193. };
  194. });
  195. return (
  196. <div>
  197. <Button onClick={this.handleAdd} type="primary" style={{ marginBottom: 16 }}>
  198. Add a row
  199. </Button>
  200. <Table
  201. components={components}
  202. rowClassName={() => 'editable-row'}
  203. bordered
  204. dataSource={dataSource}
  205. columns={columns as ColumnTypes}
  206. />
  207. </div>
  208. );
  209. }
  210. }
  211. ReactDOM.render(<EditableTable />, mountNode);
  1. .editable-cell {
  2. position: relative;
  3. }
  4. .editable-cell-value-wrap {
  5. padding: 5px 12px;
  6. cursor: pointer;
  7. }
  8. .editable-row:hover .editable-cell-value-wrap {
  9. padding: 4px 11px;
  10. border: 1px solid #d9d9d9;
  11. border-radius: 4px;
  12. }
  13. [data-theme='dark'] .editable-row:hover .editable-cell-value-wrap {
  14. border: 1px solid #434343;
  15. }

Table表格 - 图23

可编辑行

带行编辑功能的表格。

🛎️ 想要 3 分钟实现?试试 ProTable 的可编辑表格

TypeScript

JavaScript

Table表格 - 图24

  1. import React, { useState } from 'react';
  2. import { Table, Input, InputNumber, Popconfirm, Form, Typography } from 'antd';
  3. interface Item {
  4. key: string;
  5. name: string;
  6. age: number;
  7. address: string;
  8. }
  9. const originData: Item[] = [];
  10. for (let i = 0; i < 100; i++) {
  11. originData.push({
  12. key: i.toString(),
  13. name: `Edrward ${i}`,
  14. age: 32,
  15. address: `London Park no. ${i}`,
  16. });
  17. }
  18. interface EditableCellProps extends React.HTMLAttributes<HTMLElement> {
  19. editing: boolean;
  20. dataIndex: string;
  21. title: any;
  22. inputType: 'number' | 'text';
  23. record: Item;
  24. index: number;
  25. children: React.ReactNode;
  26. }
  27. const EditableCell: React.FC<EditableCellProps> = ({
  28. editing,
  29. dataIndex,
  30. title,
  31. inputType,
  32. record,
  33. index,
  34. children,
  35. ...restProps
  36. }) => {
  37. const inputNode = inputType === 'number' ? <InputNumber /> : <Input />;
  38. return (
  39. <td {...restProps}>
  40. {editing ? (
  41. <Form.Item
  42. name={dataIndex}
  43. style={{ margin: 0 }}
  44. rules={[
  45. {
  46. required: true,
  47. message: `Please Input ${title}!`,
  48. },
  49. ]}
  50. >
  51. {inputNode}
  52. </Form.Item>
  53. ) : (
  54. children
  55. )}
  56. </td>
  57. );
  58. };
  59. const EditableTable = () => {
  60. const [form] = Form.useForm();
  61. const [data, setData] = useState(originData);
  62. const [editingKey, setEditingKey] = useState('');
  63. const isEditing = (record: Item) => record.key === editingKey;
  64. const edit = (record: Partial<Item> & { key: React.Key }) => {
  65. form.setFieldsValue({ name: '', age: '', address: '', ...record });
  66. setEditingKey(record.key);
  67. };
  68. const cancel = () => {
  69. setEditingKey('');
  70. };
  71. const save = async (key: React.Key) => {
  72. try {
  73. const row = (await form.validateFields()) as Item;
  74. const newData = [...data];
  75. const index = newData.findIndex(item => key === item.key);
  76. if (index > -1) {
  77. const item = newData[index];
  78. newData.splice(index, 1, {
  79. ...item,
  80. ...row,
  81. });
  82. setData(newData);
  83. setEditingKey('');
  84. } else {
  85. newData.push(row);
  86. setData(newData);
  87. setEditingKey('');
  88. }
  89. } catch (errInfo) {
  90. console.log('Validate Failed:', errInfo);
  91. }
  92. };
  93. const columns = [
  94. {
  95. title: 'name',
  96. dataIndex: 'name',
  97. width: '25%',
  98. editable: true,
  99. },
  100. {
  101. title: 'age',
  102. dataIndex: 'age',
  103. width: '15%',
  104. editable: true,
  105. },
  106. {
  107. title: 'address',
  108. dataIndex: 'address',
  109. width: '40%',
  110. editable: true,
  111. },
  112. {
  113. title: 'operation',
  114. dataIndex: 'operation',
  115. render: (_: any, record: Item) => {
  116. const editable = isEditing(record);
  117. return editable ? (
  118. <span>
  119. <a href="javascript:;" onClick={() => save(record.key)} style={{ marginRight: 8 }}>
  120. Save
  121. </a>
  122. <Popconfirm title="Sure to cancel?" onConfirm={cancel}>
  123. <a>Cancel</a>
  124. </Popconfirm>
  125. </span>
  126. ) : (
  127. <Typography.Link disabled={editingKey !== ''} onClick={() => edit(record)}>
  128. Edit
  129. </Typography.Link>
  130. );
  131. },
  132. },
  133. ];
  134. const mergedColumns = columns.map(col => {
  135. if (!col.editable) {
  136. return col;
  137. }
  138. return {
  139. ...col,
  140. onCell: (record: Item) => ({
  141. record,
  142. inputType: col.dataIndex === 'age' ? 'number' : 'text',
  143. dataIndex: col.dataIndex,
  144. title: col.title,
  145. editing: isEditing(record),
  146. }),
  147. };
  148. });
  149. return (
  150. <Form form={form} component={false}>
  151. <Table
  152. components={{
  153. body: {
  154. cell: EditableCell,
  155. },
  156. }}
  157. bordered
  158. dataSource={data}
  159. columns={mergedColumns}
  160. rowClassName="editable-row"
  161. pagination={{
  162. onChange: cancel,
  163. }}
  164. />
  165. </Form>
  166. );
  167. };
  168. ReactDOM.render(<EditableTable />, mountNode);
  1. .editable-row .ant-form-item-explain {
  2. position: absolute;
  3. top: 100%;
  4. font-size: 12px;
  5. }

Table表格 - 图25

嵌套子表格

展示每行数据更详细的信息。

  1. import { Table, Badge, Menu, Dropdown, Space } from 'antd';
  2. import { DownOutlined } from '@ant-design/icons';
  3. const menu = (
  4. <Menu>
  5. <Menu.Item>Action 1</Menu.Item>
  6. <Menu.Item>Action 2</Menu.Item>
  7. </Menu>
  8. );
  9. function NestedTable() {
  10. const expandedRowRender = () => {
  11. const columns = [
  12. { title: 'Date', dataIndex: 'date', key: 'date' },
  13. { title: 'Name', dataIndex: 'name', key: 'name' },
  14. {
  15. title: 'Status',
  16. key: 'state',
  17. render: () => (
  18. <span>
  19. <Badge status="success" />
  20. Finished
  21. </span>
  22. ),
  23. },
  24. { title: 'Upgrade Status', dataIndex: 'upgradeNum', key: 'upgradeNum' },
  25. {
  26. title: 'Action',
  27. dataIndex: 'operation',
  28. key: 'operation',
  29. render: () => (
  30. <Space size="middle">
  31. <a>Pause</a>
  32. <a>Stop</a>
  33. <Dropdown overlay={menu}>
  34. <a>
  35. More <DownOutlined />
  36. </a>
  37. </Dropdown>
  38. </Space>
  39. ),
  40. },
  41. ];
  42. const data = [];
  43. for (let i = 0; i < 3; ++i) {
  44. data.push({
  45. key: i,
  46. date: '2014-12-24 23:12:00',
  47. name: 'This is production name',
  48. upgradeNum: 'Upgraded: 56',
  49. });
  50. }
  51. return <Table columns={columns} dataSource={data} pagination={false} />;
  52. };
  53. const columns = [
  54. { title: 'Name', dataIndex: 'name', key: 'name' },
  55. { title: 'Platform', dataIndex: 'platform', key: 'platform' },
  56. { title: 'Version', dataIndex: 'version', key: 'version' },
  57. { title: 'Upgraded', dataIndex: 'upgradeNum', key: 'upgradeNum' },
  58. { title: 'Creator', dataIndex: 'creator', key: 'creator' },
  59. { title: 'Date', dataIndex: 'createdAt', key: 'createdAt' },
  60. { title: 'Action', key: 'operation', render: () => <a>Publish</a> },
  61. ];
  62. const data = [];
  63. for (let i = 0; i < 3; ++i) {
  64. data.push({
  65. key: i,
  66. name: 'Screem',
  67. platform: 'iOS',
  68. version: '10.3.4.5654',
  69. upgradeNum: 500,
  70. creator: 'Jack',
  71. createdAt: '2014-12-24 23:12:00',
  72. });
  73. }
  74. return (
  75. <Table
  76. className="components-table-demo-nested"
  77. columns={columns}
  78. expandable={{ expandedRowRender }}
  79. dataSource={data}
  80. />
  81. );
  82. }
  83. ReactDOM.render(<NestedTable />, mountNode);

Table表格 - 图26

拖拽排序

使用自定义元素,我们可以集成 react-dnd 来实现拖拽排序。

  1. import React, { useState, useCallback, useRef } from 'react';
  2. import { Table } from 'antd';
  3. import { DndProvider, useDrag, useDrop } from 'react-dnd';
  4. import { HTML5Backend } from 'react-dnd-html5-backend';
  5. import update from 'immutability-helper';
  6. const type = 'DragableBodyRow';
  7. const DragableBodyRow = ({ index, moveRow, className, style, ...restProps }) => {
  8. const ref = useRef();
  9. const [{ isOver, dropClassName }, drop] = useDrop({
  10. accept: type,
  11. collect: monitor => {
  12. const { index: dragIndex } = monitor.getItem() || {};
  13. if (dragIndex === index) {
  14. return {};
  15. }
  16. return {
  17. isOver: monitor.isOver(),
  18. dropClassName: dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',
  19. };
  20. },
  21. drop: item => {
  22. moveRow(item.index, index);
  23. },
  24. });
  25. const [, drag] = useDrag({
  26. type,
  27. item: { index },
  28. collect: monitor => ({
  29. isDragging: monitor.isDragging(),
  30. }),
  31. });
  32. drop(drag(ref));
  33. return (
  34. <tr
  35. ref={ref}
  36. className={`${className}${isOver ? dropClassName : ''}`}
  37. style={{ cursor: 'move', ...style }}
  38. {...restProps}
  39. />
  40. );
  41. };
  42. const columns = [
  43. {
  44. title: 'Name',
  45. dataIndex: 'name',
  46. key: 'name',
  47. },
  48. {
  49. title: 'Age',
  50. dataIndex: 'age',
  51. key: 'age',
  52. },
  53. {
  54. title: 'Address',
  55. dataIndex: 'address',
  56. key: 'address',
  57. },
  58. ];
  59. const DragSortingTable: React.FC = () => {
  60. const [data, setData] = useState([
  61. {
  62. key: '1',
  63. name: 'John Brown',
  64. age: 32,
  65. address: 'New York No. 1 Lake Park',
  66. },
  67. {
  68. key: '2',
  69. name: 'Jim Green',
  70. age: 42,
  71. address: 'London No. 1 Lake Park',
  72. },
  73. {
  74. key: '3',
  75. name: 'Joe Black',
  76. age: 32,
  77. address: 'Sidney No. 1 Lake Park',
  78. },
  79. ]);
  80. const components = {
  81. body: {
  82. row: DragableBodyRow,
  83. },
  84. };
  85. const moveRow = useCallback(
  86. (dragIndex, hoverIndex) => {
  87. const dragRow = data[dragIndex];
  88. setData(
  89. update(data, {
  90. $splice: [
  91. [dragIndex, 1],
  92. [hoverIndex, 0, dragRow],
  93. ],
  94. }),
  95. );
  96. },
  97. [data],
  98. );
  99. return (
  100. <DndProvider backend={HTML5Backend}>
  101. <Table
  102. columns={columns}
  103. dataSource={data}
  104. components={components}
  105. onRow={(record, index) => ({
  106. index,
  107. moveRow,
  108. })}
  109. />
  110. </DndProvider>
  111. );
  112. };
  113. ReactDOM.render(<DragSortingTable />, mountNode);
  1. #components-table-demo-drag-sorting tr.drop-over-downward td {
  2. border-bottom: 2px dashed #1890ff;
  3. }
  4. #components-table-demo-drag-sorting tr.drop-over-upward td {
  5. border-top: 2px dashed #1890ff;
  6. }

Table表格 - 图27

拖拽手柄列

也可以使用 react-sortable-hoc 来实现一个拖拽操作列。

  1. import { Table } from 'antd';
  2. import { sortableContainer, sortableElement, sortableHandle } from 'react-sortable-hoc';
  3. import { MenuOutlined } from '@ant-design/icons';
  4. import arrayMove from 'array-move';
  5. const DragHandle = sortableHandle(() => <MenuOutlined style={{ cursor: 'grab', color: '#999' }} />);
  6. const columns = [
  7. {
  8. title: 'Sort',
  9. dataIndex: 'sort',
  10. width: 30,
  11. className: 'drag-visible',
  12. render: () => <DragHandle />,
  13. },
  14. {
  15. title: 'Name',
  16. dataIndex: 'name',
  17. className: 'drag-visible',
  18. },
  19. {
  20. title: 'Age',
  21. dataIndex: 'age',
  22. },
  23. {
  24. title: 'Address',
  25. dataIndex: 'address',
  26. },
  27. ];
  28. const data = [
  29. {
  30. key: '1',
  31. name: 'John Brown',
  32. age: 32,
  33. address: 'New York No. 1 Lake Park',
  34. index: 0,
  35. },
  36. {
  37. key: '2',
  38. name: 'Jim Green',
  39. age: 42,
  40. address: 'London No. 1 Lake Park',
  41. index: 1,
  42. },
  43. {
  44. key: '3',
  45. name: 'Joe Black',
  46. age: 32,
  47. address: 'Sidney No. 1 Lake Park',
  48. index: 2,
  49. },
  50. ];
  51. const SortableItem = sortableElement(props => <tr {...props} />);
  52. const SortableContainer = sortableContainer(props => <tbody {...props} />);
  53. class SortableTable extends React.Component {
  54. state = {
  55. dataSource: data,
  56. };
  57. onSortEnd = ({ oldIndex, newIndex }) => {
  58. const { dataSource } = this.state;
  59. if (oldIndex !== newIndex) {
  60. const newData = arrayMove([].concat(dataSource), oldIndex, newIndex).filter(el => !!el);
  61. console.log('Sorted items: ', newData);
  62. this.setState({ dataSource: newData });
  63. }
  64. };
  65. DraggableContainer = props => (
  66. <SortableContainer
  67. useDragHandle
  68. disableAutoscroll
  69. helperClass="row-dragging"
  70. onSortEnd={this.onSortEnd}
  71. {...props}
  72. />
  73. );
  74. DraggableBodyRow = ({ className, style, ...restProps }) => {
  75. const { dataSource } = this.state;
  76. // function findIndex base on Table rowKey props and should always be a right array index
  77. const index = dataSource.findIndex(x => x.index === restProps['data-row-key']);
  78. return <SortableItem index={index} {...restProps} />;
  79. };
  80. render() {
  81. const { dataSource } = this.state;
  82. return (
  83. <Table
  84. pagination={false}
  85. dataSource={dataSource}
  86. columns={columns}
  87. rowKey="index"
  88. components={{
  89. body: {
  90. wrapper: this.DraggableContainer,
  91. row: this.DraggableBodyRow,
  92. },
  93. }}
  94. />
  95. );
  96. }
  97. }
  98. ReactDOM.render(<SortableTable />, mountNode);
  1. .row-dragging {
  2. background: #fafafa;
  3. border: 1px solid #ccc;
  4. }
  5. .row-dragging td {
  6. padding: 16px;
  7. }
  8. .row-dragging .drag-visible {
  9. visibility: visible;
  10. }

Table表格 - 图28

单元格自动省略

设置 column.ellipsis 可以让单元格内容根据宽度自动省略。

列头缩略暂不支持和排序筛选一起使用。

  1. import { Table } from 'antd';
  2. const columns = [
  3. {
  4. title: 'Name',
  5. dataIndex: 'name',
  6. key: 'name',
  7. render: text => <a>{text}</a>,
  8. width: 150,
  9. },
  10. {
  11. title: 'Age',
  12. dataIndex: 'age',
  13. key: 'age',
  14. width: 80,
  15. },
  16. {
  17. title: 'Address',
  18. dataIndex: 'address',
  19. key: 'address 1',
  20. ellipsis: true,
  21. },
  22. {
  23. title: 'Long Column Long Column Long Column',
  24. dataIndex: 'address',
  25. key: 'address 2',
  26. ellipsis: true,
  27. },
  28. {
  29. title: 'Long Column Long Column',
  30. dataIndex: 'address',
  31. key: 'address 3',
  32. ellipsis: true,
  33. },
  34. {
  35. title: 'Long Column',
  36. dataIndex: 'address',
  37. key: 'address 4',
  38. ellipsis: true,
  39. },
  40. ];
  41. const data = [
  42. {
  43. key: '1',
  44. name: 'John Brown',
  45. age: 32,
  46. address: 'New York No. 1 Lake Park, New York No. 1 Lake Park',
  47. tags: ['nice', 'developer'],
  48. },
  49. {
  50. key: '2',
  51. name: 'Jim Green',
  52. age: 42,
  53. address: 'London No. 2 Lake Park, London No. 2 Lake Park',
  54. tags: ['loser'],
  55. },
  56. {
  57. key: '3',
  58. name: 'Joe Black',
  59. age: 32,
  60. address: 'Sidney No. 1 Lake Park, Sidney No. 1 Lake Park',
  61. tags: ['cool', 'teacher'],
  62. },
  63. ];
  64. ReactDOM.render(<Table columns={columns} dataSource={data} />, mountNode);

Table表格 - 图29

自定义单元格省略提示

设置 column.ellipsis.showTitle 关闭单元格内容自动省略后默认的 title 提示, 使用 Tooltip 替代。

  1. import { Table, Tooltip } from 'antd';
  2. const columns = [
  3. {
  4. title: 'Name',
  5. dataIndex: 'name',
  6. key: 'name',
  7. render: text => <a>{text}</a>,
  8. width: 150,
  9. },
  10. {
  11. title: 'Age',
  12. dataIndex: 'age',
  13. key: 'age',
  14. width: 80,
  15. },
  16. {
  17. title: 'Address',
  18. dataIndex: 'address',
  19. key: 'address 1',
  20. ellipsis: {
  21. showTitle: false,
  22. },
  23. render: address => (
  24. <Tooltip placement="topLeft" title={address}>
  25. {address}
  26. </Tooltip>
  27. ),
  28. },
  29. {
  30. title: 'Long Column Long Column Long Column',
  31. dataIndex: 'address',
  32. key: 'address 2',
  33. ellipsis: {
  34. showTitle: false,
  35. },
  36. render: address => (
  37. <Tooltip placement="topLeft" title={address}>
  38. {address}
  39. </Tooltip>
  40. ),
  41. },
  42. {
  43. title: 'Long Column Long Column',
  44. dataIndex: 'address',
  45. key: 'address 3',
  46. ellipsis: {
  47. showTitle: false,
  48. },
  49. render: address => (
  50. <Tooltip placement="topLeft" title={address}>
  51. {address}
  52. </Tooltip>
  53. ),
  54. },
  55. {
  56. title: 'Long Column',
  57. dataIndex: 'address',
  58. key: 'address 4',
  59. ellipsis: {
  60. showTitle: false,
  61. },
  62. render: address => (
  63. <Tooltip placement="topLeft" title={address}>
  64. {address}
  65. </Tooltip>
  66. ),
  67. },
  68. ];
  69. const data = [
  70. {
  71. key: '1',
  72. name: 'John Brown',
  73. age: 32,
  74. address: 'New York No. 1 Lake Park, New York No. 1 Lake Park',
  75. },
  76. {
  77. key: '2',
  78. name: 'Jim Green',
  79. age: 42,
  80. address: 'London No. 2 Lake Park, London No. 2 Lake Park',
  81. },
  82. {
  83. key: '3',
  84. name: 'Joe Black',
  85. age: 32,
  86. address: 'Sidney No. 1 Lake Park, Sidney No. 1 Lake Park',
  87. },
  88. ];
  89. ReactDOM.render(<Table columns={columns} dataSource={data} />, mountNode);

Table表格 - 图30

总结栏

通过 summary 设置总结栏。使用 Table.Summary.Cell 同步 Column 的固定状态。你可以通过配置 Table.Summaryfixed 属性使其固定(4.16.0 支持)。

  1. import { Table, Typography } from 'antd';
  2. const { Text } = Typography;
  3. const columns = [
  4. {
  5. title: 'Name',
  6. dataIndex: 'name',
  7. },
  8. {
  9. title: 'Borrow',
  10. dataIndex: 'borrow',
  11. },
  12. {
  13. title: 'Repayment',
  14. dataIndex: 'repayment',
  15. },
  16. ];
  17. const data = [
  18. {
  19. key: '1',
  20. name: 'John Brown',
  21. borrow: 10,
  22. repayment: 33,
  23. },
  24. {
  25. key: '2',
  26. name: 'Jim Green',
  27. borrow: 100,
  28. repayment: 0,
  29. },
  30. {
  31. key: '3',
  32. name: 'Joe Black',
  33. borrow: 10,
  34. repayment: 10,
  35. },
  36. {
  37. key: '4',
  38. name: 'Jim Red',
  39. borrow: 75,
  40. repayment: 45,
  41. },
  42. ];
  43. const fixedColumns = [
  44. {
  45. title: 'Name',
  46. dataIndex: 'name',
  47. fixed: true,
  48. width: 100,
  49. },
  50. {
  51. title: 'Description',
  52. dataIndex: 'description',
  53. },
  54. ];
  55. const fixedData = [];
  56. for (let i = 0; i < 20; i += 1) {
  57. fixedData.push({
  58. key: i,
  59. name: ['Light', 'Bamboo', 'Little'][i % 3],
  60. description: 'Everything that has a beginning, has an end.',
  61. });
  62. }
  63. ReactDOM.render(
  64. <>
  65. <Table
  66. columns={columns}
  67. dataSource={data}
  68. pagination={false}
  69. bordered
  70. summary={pageData => {
  71. let totalBorrow = 0;
  72. let totalRepayment = 0;
  73. pageData.forEach(({ borrow, repayment }) => {
  74. totalBorrow += borrow;
  75. totalRepayment += repayment;
  76. });
  77. return (
  78. <>
  79. <Table.Summary.Row>
  80. <Table.Summary.Cell>Total</Table.Summary.Cell>
  81. <Table.Summary.Cell>
  82. <Text type="danger">{totalBorrow}</Text>
  83. </Table.Summary.Cell>
  84. <Table.Summary.Cell>
  85. <Text>{totalRepayment}</Text>
  86. </Table.Summary.Cell>
  87. </Table.Summary.Row>
  88. <Table.Summary.Row>
  89. <Table.Summary.Cell>Balance</Table.Summary.Cell>
  90. <Table.Summary.Cell colSpan={2}>
  91. <Text type="danger">{totalBorrow - totalRepayment}</Text>
  92. </Table.Summary.Cell>
  93. </Table.Summary.Row>
  94. </>
  95. );
  96. }}
  97. />
  98. <br />
  99. <Table
  100. columns={fixedColumns}
  101. dataSource={fixedData}
  102. pagination={false}
  103. scroll={{ x: 2000, y: 500 }}
  104. bordered
  105. summary={() => (
  106. <Table.Summary fixed>
  107. <Table.Summary.Row>
  108. <Table.Summary.Cell index={0}>Summary</Table.Summary.Cell>
  109. <Table.Summary.Cell index={1}>This is a summary content</Table.Summary.Cell>
  110. </Table.Summary.Row>
  111. </Table.Summary>
  112. )}
  113. />
  114. </>,
  115. mountNode,
  116. );

Table表格 - 图31

虚拟列表

通过 react-window 引入虚拟滚动方案,实现 100000 条数据的高性能表格。

TypeScript

JavaScript

Table表格 - 图32

  1. import React, { useState, useEffect, useRef } from 'react';
  2. import { VariableSizeGrid as Grid } from 'react-window';
  3. import ResizeObserver from 'rc-resize-observer';
  4. import classNames from 'classnames';
  5. import { Table } from 'antd';
  6. function VirtualTable(props: Parameters<typeof Table>[0]) {
  7. const { columns, scroll } = props;
  8. const [tableWidth, setTableWidth] = useState(0);
  9. const widthColumnCount = columns!.filter(({ width }) => !width).length;
  10. const mergedColumns = columns!.map(column => {
  11. if (column.width) {
  12. return column;
  13. }
  14. return {
  15. ...column,
  16. width: Math.floor(tableWidth / widthColumnCount),
  17. };
  18. });
  19. const gridRef = useRef<any>();
  20. const [connectObject] = useState<any>(() => {
  21. const obj = {};
  22. Object.defineProperty(obj, 'scrollLeft', {
  23. get: () => null,
  24. set: (scrollLeft: number) => {
  25. if (gridRef.current) {
  26. gridRef.current.scrollTo({ scrollLeft });
  27. }
  28. },
  29. });
  30. return obj;
  31. });
  32. const resetVirtualGrid = () => {
  33. gridRef.current.resetAfterIndices({
  34. columnIndex: 0,
  35. shouldForceUpdate: true,
  36. });
  37. };
  38. useEffect(() => resetVirtualGrid, [tableWidth]);
  39. const renderVirtualList = (rawData: object[], { scrollbarSize, ref, onScroll }: any) => {
  40. ref.current = connectObject;
  41. const totalHeight = rawData.length * 54;
  42. return (
  43. <Grid
  44. ref={gridRef}
  45. className="virtual-grid"
  46. columnCount={mergedColumns.length}
  47. columnWidth={(index: number) => {
  48. const { width } = mergedColumns[index];
  49. return totalHeight > scroll!.y! && index === mergedColumns.length - 1
  50. ? (width as number) - scrollbarSize - 1
  51. : (width as number);
  52. }}
  53. height={scroll!.y as number}
  54. rowCount={rawData.length}
  55. rowHeight={() => 54}
  56. width={tableWidth}
  57. onScroll={({ scrollLeft }: { scrollLeft: number }) => {
  58. onScroll({ scrollLeft });
  59. }}
  60. >
  61. {({
  62. columnIndex,
  63. rowIndex,
  64. style,
  65. }: {
  66. columnIndex: number;
  67. rowIndex: number;
  68. style: React.CSSProperties;
  69. }) => (
  70. <div
  71. className={classNames('virtual-table-cell', {
  72. 'virtual-table-cell-last': columnIndex === mergedColumns.length - 1,
  73. })}
  74. style={style}
  75. >
  76. {(rawData[rowIndex] as any)[(mergedColumns as any)[columnIndex].dataIndex]}
  77. </div>
  78. )}
  79. </Grid>
  80. );
  81. };
  82. return (
  83. <ResizeObserver
  84. onResize={({ width }) => {
  85. setTableWidth(width);
  86. }}
  87. >
  88. <Table
  89. {...props}
  90. className="virtual-table"
  91. columns={mergedColumns}
  92. pagination={false}
  93. components={{
  94. body: renderVirtualList,
  95. }}
  96. />
  97. </ResizeObserver>
  98. );
  99. }
  100. // Usage
  101. const columns = [
  102. { title: 'A', dataIndex: 'key', width: 150 },
  103. { title: 'B', dataIndex: 'key' },
  104. { title: 'C', dataIndex: 'key' },
  105. { title: 'D', dataIndex: 'key' },
  106. { title: 'E', dataIndex: 'key', width: 200 },
  107. { title: 'F', dataIndex: 'key', width: 100 },
  108. ];
  109. const data = Array.from({ length: 100000 }, (_, key) => ({ key }));
  110. ReactDOM.render(
  111. <VirtualTable columns={columns} dataSource={data} scroll={{ y: 300, x: '100vw' }} />,
  112. mountNode,
  113. );

Table表格 - 图33

响应式

响应式配置列的展示。

  1. import { Table } from 'antd';
  2. const columns = [
  3. {
  4. title: 'Name (all screens)',
  5. dataIndex: 'name',
  6. key: 'name',
  7. render: text => <a>{text}</a>,
  8. },
  9. {
  10. title: 'Age (medium screen or bigger)',
  11. dataIndex: 'age',
  12. key: 'age',
  13. responsive: ['md'],
  14. },
  15. {
  16. title: 'Address (large screen or bigger)',
  17. dataIndex: 'address',
  18. key: 'address',
  19. responsive: ['lg'],
  20. },
  21. ];
  22. const data = [
  23. {
  24. key: '1',
  25. name: 'John Brown',
  26. age: 32,
  27. address: 'New York No. 1 Lake Park',
  28. },
  29. ];
  30. ReactDOM.render(<Table columns={columns} dataSource={data} />, mountNode);

Table表格 - 图34

分页设置

表格的分页设置。

  1. import { Table, Tag, Radio, Space } from 'antd';
  2. const topOptions = [
  3. { label: 'topLeft', value: 'topLeft' },
  4. { label: 'topCenter', value: 'topCenter' },
  5. { label: 'topRight', value: 'topRight' },
  6. { label: 'none', value: 'none' },
  7. ];
  8. const bottomOptions = [
  9. { label: 'bottomLeft', value: 'bottomLeft' },
  10. { label: 'bottomCenter', value: 'bottomCenter' },
  11. { label: 'bottomRight', value: 'bottomRight' },
  12. { label: 'none', value: 'none' },
  13. ];
  14. const columns = [
  15. {
  16. title: 'Name',
  17. dataIndex: 'name',
  18. key: 'name',
  19. render: text => <a>{text}</a>,
  20. },
  21. {
  22. title: 'Age',
  23. dataIndex: 'age',
  24. key: 'age',
  25. },
  26. {
  27. title: 'Address',
  28. dataIndex: 'address',
  29. key: 'address',
  30. },
  31. {
  32. title: 'Tags',
  33. key: 'tags',
  34. dataIndex: 'tags',
  35. render: tags => (
  36. <span>
  37. {tags.map(tag => {
  38. let color = tag.length > 5 ? 'geekblue' : 'green';
  39. if (tag === 'loser') {
  40. color = 'volcano';
  41. }
  42. return (
  43. <Tag color={color} key={tag}>
  44. {tag.toUpperCase()}
  45. </Tag>
  46. );
  47. })}
  48. </span>
  49. ),
  50. },
  51. {
  52. title: 'Action',
  53. key: 'action',
  54. render: (text, record) => (
  55. <Space size="middle">
  56. <a>Invite {record.name}</a>
  57. <a>Delete</a>
  58. </Space>
  59. ),
  60. },
  61. ];
  62. const data = [
  63. {
  64. key: '1',
  65. name: 'John Brown',
  66. age: 32,
  67. address: 'New York No. 1 Lake Park',
  68. tags: ['nice', 'developer'],
  69. },
  70. {
  71. key: '2',
  72. name: 'Jim Green',
  73. age: 42,
  74. address: 'London No. 1 Lake Park',
  75. tags: ['loser'],
  76. },
  77. {
  78. key: '3',
  79. name: 'Joe Black',
  80. age: 32,
  81. address: 'Sidney No. 1 Lake Park',
  82. tags: ['cool', 'teacher'],
  83. },
  84. ];
  85. class Demo extends React.Component {
  86. state = {
  87. top: 'topLeft',
  88. bottom: 'bottomRight',
  89. };
  90. render() {
  91. return (
  92. <div>
  93. <div>
  94. <Radio.Group
  95. style={{ marginBottom: 10 }}
  96. options={topOptions}
  97. value={this.state.top}
  98. onChange={e => {
  99. this.setState({ top: e.target.value });
  100. }}
  101. />
  102. </div>
  103. <Radio.Group
  104. style={{ marginBottom: 10 }}
  105. options={bottomOptions}
  106. value={this.state.bottom}
  107. onChange={e => {
  108. this.setState({ bottom: e.target.value });
  109. }}
  110. />
  111. <Table
  112. columns={columns}
  113. pagination={{ position: [this.state.top, this.state.bottom] }}
  114. dataSource={data}
  115. />
  116. </div>
  117. );
  118. }
  119. }
  120. ReactDOM.render(<Demo />, mountNode);

Table表格 - 图35

随页面滚动的固定表头和滚动条

对于长表格,需要滚动才能查看表头和滚动条,那么现在可以设置跟随页面固定表头和滚动条。

  1. import { Table } from 'antd';
  2. const columns = [
  3. {
  4. title: 'Full Name',
  5. width: 100,
  6. dataIndex: 'name',
  7. key: 'name',
  8. fixed: 'left',
  9. },
  10. {
  11. title: 'Age',
  12. width: 100,
  13. dataIndex: 'age',
  14. key: 'age',
  15. fixed: 'left',
  16. },
  17. {
  18. title: 'Column 1',
  19. dataIndex: 'address',
  20. key: '1',
  21. width: 150,
  22. },
  23. {
  24. title: 'Column 2',
  25. dataIndex: 'address',
  26. key: '2',
  27. width: 150,
  28. },
  29. {
  30. title: 'Column 3',
  31. dataIndex: 'address',
  32. key: '3',
  33. width: 150,
  34. },
  35. {
  36. title: 'Column 4',
  37. dataIndex: 'address',
  38. key: '4',
  39. width: 150,
  40. },
  41. {
  42. title: 'Column 5',
  43. dataIndex: 'address',
  44. key: '5',
  45. width: 150,
  46. },
  47. {
  48. title: 'Column 6',
  49. dataIndex: 'address',
  50. key: '6',
  51. width: 150,
  52. },
  53. {
  54. title: 'Column 7',
  55. dataIndex: 'address',
  56. key: '7',
  57. width: 150,
  58. },
  59. { title: 'Column 8', dataIndex: 'address', key: '8' },
  60. {
  61. title: 'Action',
  62. key: 'operation',
  63. fixed: 'right',
  64. width: 100,
  65. render: () => <a>action</a>,
  66. },
  67. ];
  68. const data = [];
  69. for (let i = 0; i < 100; i++) {
  70. data.push({
  71. key: i,
  72. name: `Edrward ${i}`,
  73. age: 32,
  74. address: `London Park no. ${i}`,
  75. });
  76. }
  77. ReactDOM.render(
  78. <Table
  79. columns={columns}
  80. dataSource={data}
  81. scroll={{ x: 1500 }}
  82. summary={pageData => (
  83. <Table.Summary fixed>
  84. <Table.Summary.Row>
  85. <Table.Summary.Cell index={0} colSpan={2}>
  86. Fix Left
  87. </Table.Summary.Cell>
  88. <Table.Summary.Cell index={2} colSpan={8}>
  89. Scroll Context
  90. </Table.Summary.Cell>
  91. <Table.Summary.Cell index={10}>Fix Right</Table.Summary.Cell>
  92. </Table.Summary.Row>
  93. </Table.Summary>
  94. )}
  95. sticky
  96. />,
  97. mountNode,
  98. );

Table表格 - 图36

动态控制表格属性

选择不同配置组合查看效果。

  1. import { Table, Switch, Radio, Form, Space } from 'antd';
  2. import { DownOutlined } from '@ant-design/icons';
  3. const columns = [
  4. {
  5. title: 'Name',
  6. dataIndex: 'name',
  7. },
  8. {
  9. title: 'Age',
  10. dataIndex: 'age',
  11. sorter: (a, b) => a.age - b.age,
  12. },
  13. {
  14. title: 'Address',
  15. dataIndex: 'address',
  16. filters: [
  17. {
  18. text: 'London',
  19. value: 'London',
  20. },
  21. {
  22. text: 'New York',
  23. value: 'New York',
  24. },
  25. ],
  26. onFilter: (value, record) => record.address.indexOf(value) === 0,
  27. },
  28. {
  29. title: 'Action',
  30. key: 'action',
  31. sorter: true,
  32. render: () => (
  33. <Space size="middle">
  34. <a>Delete</a>
  35. <a className="ant-dropdown-link">
  36. More actions <DownOutlined />
  37. </a>
  38. </Space>
  39. ),
  40. },
  41. ];
  42. const data = [];
  43. for (let i = 1; i <= 10; i++) {
  44. data.push({
  45. key: i,
  46. name: 'John Brown',
  47. age: `${i}2`,
  48. address: `New York No. ${i} Lake Park`,
  49. description: `My name is John Brown, I am ${i}2 years old, living in New York No. ${i} Lake Park.`,
  50. });
  51. }
  52. const expandable = { expandedRowRender: record => <p>{record.description}</p> };
  53. const title = () => 'Here is title';
  54. const showHeader = true;
  55. const footer = () => 'Here is footer';
  56. const pagination = { position: 'bottom' };
  57. class Demo extends React.Component {
  58. state = {
  59. bordered: false,
  60. loading: false,
  61. pagination,
  62. size: 'default',
  63. expandable,
  64. title: undefined,
  65. showHeader,
  66. footer,
  67. rowSelection: {},
  68. scroll: undefined,
  69. hasData: true,
  70. tableLayout: undefined,
  71. top: 'none',
  72. bottom: 'bottomRight',
  73. };
  74. handleToggle = prop => enable => {
  75. this.setState({ [prop]: enable });
  76. };
  77. handleSizeChange = e => {
  78. this.setState({ size: e.target.value });
  79. };
  80. handleTableLayoutChange = e => {
  81. this.setState({ tableLayout: e.target.value });
  82. };
  83. handleExpandChange = enable => {
  84. this.setState({ expandable: enable ? expandable : undefined });
  85. };
  86. handleEllipsisChange = enable => {
  87. this.setState({ ellipsis: enable });
  88. };
  89. handleTitleChange = enable => {
  90. this.setState({ title: enable ? title : undefined });
  91. };
  92. handleHeaderChange = enable => {
  93. this.setState({ showHeader: enable ? showHeader : false });
  94. };
  95. handleFooterChange = enable => {
  96. this.setState({ footer: enable ? footer : undefined });
  97. };
  98. handleRowSelectionChange = enable => {
  99. this.setState({ rowSelection: enable ? {} : undefined });
  100. };
  101. handleYScrollChange = enable => {
  102. this.setState({ yScroll: enable });
  103. };
  104. handleXScrollChange = e => {
  105. this.setState({ xScroll: e.target.value });
  106. };
  107. handleDataChange = hasData => {
  108. this.setState({ hasData });
  109. };
  110. render() {
  111. const { xScroll, yScroll, ...state } = this.state;
  112. const scroll = {};
  113. if (yScroll) {
  114. scroll.y = 240;
  115. }
  116. if (xScroll) {
  117. scroll.x = '100vw';
  118. }
  119. const tableColumns = columns.map(item => ({ ...item, ellipsis: state.ellipsis }));
  120. if (xScroll === 'fixed') {
  121. tableColumns[0].fixed = true;
  122. tableColumns[tableColumns.length - 1].fixed = 'right';
  123. }
  124. return (
  125. <>
  126. <Form
  127. layout="inline"
  128. className="components-table-demo-control-bar"
  129. style={{ marginBottom: 16 }}
  130. >
  131. <Form.Item label="Bordered">
  132. <Switch checked={state.bordered} onChange={this.handleToggle('bordered')} />
  133. </Form.Item>
  134. <Form.Item label="loading">
  135. <Switch checked={state.loading} onChange={this.handleToggle('loading')} />
  136. </Form.Item>
  137. <Form.Item label="Title">
  138. <Switch checked={!!state.title} onChange={this.handleTitleChange} />
  139. </Form.Item>
  140. <Form.Item label="Column Header">
  141. <Switch checked={!!state.showHeader} onChange={this.handleHeaderChange} />
  142. </Form.Item>
  143. <Form.Item label="Footer">
  144. <Switch checked={!!state.footer} onChange={this.handleFooterChange} />
  145. </Form.Item>
  146. <Form.Item label="Expandable">
  147. <Switch checked={!!state.expandable} onChange={this.handleExpandChange} />
  148. </Form.Item>
  149. <Form.Item label="Checkbox">
  150. <Switch checked={!!state.rowSelection} onChange={this.handleRowSelectionChange} />
  151. </Form.Item>
  152. <Form.Item label="Fixed Header">
  153. <Switch checked={!!yScroll} onChange={this.handleYScrollChange} />
  154. </Form.Item>
  155. <Form.Item label="Has Data">
  156. <Switch checked={!!state.hasData} onChange={this.handleDataChange} />
  157. </Form.Item>
  158. <Form.Item label="Ellipsis">
  159. <Switch checked={!!state.ellipsis} onChange={this.handleEllipsisChange} />
  160. </Form.Item>
  161. <Form.Item label="Size">
  162. <Radio.Group value={state.size} onChange={this.handleSizeChange}>
  163. <Radio.Button value="default">Default</Radio.Button>
  164. <Radio.Button value="middle">Middle</Radio.Button>
  165. <Radio.Button value="small">Small</Radio.Button>
  166. </Radio.Group>
  167. </Form.Item>
  168. <Form.Item label="Table Scroll">
  169. <Radio.Group value={xScroll} onChange={this.handleXScrollChange}>
  170. <Radio.Button value={undefined}>Unset</Radio.Button>
  171. <Radio.Button value="scroll">Scroll</Radio.Button>
  172. <Radio.Button value="fixed">Fixed Columns</Radio.Button>
  173. </Radio.Group>
  174. </Form.Item>
  175. <Form.Item label="Table Layout">
  176. <Radio.Group value={state.tableLayout} onChange={this.handleTableLayoutChange}>
  177. <Radio.Button value={undefined}>Unset</Radio.Button>
  178. <Radio.Button value="fixed">Fixed</Radio.Button>
  179. </Radio.Group>
  180. </Form.Item>
  181. <Form.Item label="Pagination Top">
  182. <Radio.Group
  183. value={this.state.top}
  184. onChange={e => {
  185. this.setState({ top: e.target.value });
  186. }}
  187. >
  188. <Radio.Button value="topLeft">TopLeft</Radio.Button>
  189. <Radio.Button value="topCenter">TopCenter</Radio.Button>
  190. <Radio.Button value="topRight">TopRight</Radio.Button>
  191. <Radio.Button value="none">None</Radio.Button>
  192. </Radio.Group>
  193. </Form.Item>
  194. <Form.Item label="Pagination Bottom">
  195. <Radio.Group
  196. value={this.state.bottom}
  197. onChange={e => {
  198. this.setState({ bottom: e.target.value });
  199. }}
  200. >
  201. <Radio.Button value="bottomLeft">BottomLeft</Radio.Button>
  202. <Radio.Button value="bottomCenter">BottomCenter</Radio.Button>
  203. <Radio.Button value="bottomRight">BottomRight</Radio.Button>
  204. <Radio.Button value="none">None</Radio.Button>
  205. </Radio.Group>
  206. </Form.Item>
  207. </Form>
  208. <Table
  209. {...this.state}
  210. pagination={{ position: [this.state.top, this.state.bottom] }}
  211. columns={tableColumns}
  212. dataSource={state.hasData ? data : null}
  213. scroll={scroll}
  214. />
  215. </>
  216. );
  217. }
  218. }
  219. ReactDOM.render(<Demo />, mountNode);

API

另外我们封装了 ProTable,在 antd Table 之上扩展了更多便捷易用的功能,内置搜索、筛选、刷新等常用表格行为,并为多种类型数据展示提供了内置格式化,欢迎尝试使用。

Table

参数说明类型默认值版本
bordered是否展示外边框和列边框booleanfalse
columns表格列的配置描述,具体项见下表ColumnsType[]-
components覆盖默认的 table 元素TableComponents-
dataSource数据数组object[]-
expandable配置展开属性expandable-
footer表格尾部function(currentPageData)-
getPopupContainer设置表格内各类浮层的渲染节点,如筛选菜单(triggerNode) => HTMLElement() => TableHtmlElement
loading页面是否加载中boolean | Spin Propsfalse
locale默认文案设置,目前包括排序、过滤、空数据文案objectfilterConfirm: 确定
filterReset: 重置
emptyText: 暂无数据
默认值
pagination分页器,参考配置项pagination 文档,设为 false 时不展示和进行分页object-
rowClassName表格行的类名function(record, index): string-
rowKey表格行 key 的取值,可以是字符串或一个函数string | function(record): stringkey
rowSelection表格行是否可选择,配置项object-
scroll表格是否可滚动,也可以指定滚动区域的宽、高,配置项object-
showHeader是否显示表头booleantrue
showSorterTooltip表头是否显示下一次排序的 tooltip 提示。当参数类型为对象时,将被设置为 Tooltip 的属性boolean | Tooltip propstrue
size表格大小default | middle | smalldefault
sortDirections支持的排序方式,取值为 ascend descendArray[ascend, descend]
sticky设置粘性头部和滚动条boolean | {offsetHeader?: number, offsetScroll?: number, getContainer?: () => HTMLElement}-4.6.0 (getContainer: 4.7.0)
summary总结栏(currentData) => ReactNode-
tableLayout表格元素的 table-layout 属性,设为 fixed 表示内容不会影响列的布局- | auto | fixed
固定表头/列或使用了 column.ellipsis 时,默认值为 fixed
title表格标题function(currentPageData)-
onChange分页、排序、筛选变化时触发function(pagination, filters, sorter, extra: { currentDataSource: [], action: paginate | sort | filter })-
onHeaderRow设置头部行属性function(columns, index)-
onRow设置行属性function(record, index)-

onRow 用法

适用于 onRow onHeaderRow onCell onHeaderCell

  1. <Table
  2. onRow={record => {
  3. return {
  4. onClick: event => {}, // 点击行
  5. onDoubleClick: event => {},
  6. onContextMenu: event => {},
  7. onMouseEnter: event => {}, // 鼠标移入行
  8. onMouseLeave: event => {},
  9. };
  10. }}
  11. onHeaderRow={(columns, index) => {
  12. return {
  13. onClick: () => {}, // 点击表头行
  14. };
  15. }}
  16. />

Column

列描述数据对象,是 columns 中的一项,Column 使用相同的 API。

参数说明类型默认值版本
align设置列的对齐方式left | right | centerleft
className列样式类名string-
colSpan表头列合并,设置为 0 时,不渲染number-
dataIndex列数据在数据项中对应的路径,支持通过数组查询嵌套路径string | string[]-
defaultFilteredValue默认筛选值string[]-
defaultSortOrder默认排序顺序ascend | descend-
editable是否可编辑booleanfalse
ellipsis超过宽度将自动省略,暂不支持和排序筛选一起使用。
设置为 true{ showTitle?: boolean } 时,表格布局将变成 tableLayout=”fixed”
boolean | { showTitle?: boolean }falseshowTitle: 4.3.0
filterDropdown可以自定义筛选菜单,此函数只负责渲染图层,需要自行编写各种交互ReactNode | (props: FilterDropdownProps) => ReactNode-
filterDropdownVisible用于控制自定义筛选菜单是否可见boolean-
filtered标识数据是否经过过滤,筛选图标会高亮booleanfalse
filteredValue筛选的受控属性,外界可用此控制列的筛选状态,值为已筛选的 value 数组string[]-
filterIcon自定义 filter 图标。ReactNode | (filtered: boolean) => ReactNodefalse
filterMultiple是否多选booleantrue
filters表头的筛选菜单项object[]-
fixed(IE 下无效)列是否固定,可选 true (等效于 left) left rightboolean | stringfalse
keyReact 需要的 key,如果已经设置了唯一的 dataIndex,可以忽略这个属性string-
render生成复杂数据的渲染函数,参数分别为当前行的值,当前行数据,行索引,@return 里面可以设置表格行/列合并function(text, record, index) {}-
responsive响应式 breakpoint 配置列表。未设置则始终可见。Breakpoint[]-4.2.0
shouldCellUpdate自定义单元格渲染时机(record, prevRecord) => boolean-4.3.0
showSorterTooltip表头显示下一次排序的 tooltip 提示, 覆盖 table 中 showSorterTooltipboolean | Tooltip propstrue
sortDirections支持的排序方式,覆盖 TablesortDirections, 取值为 ascend descendArray[ascend, descend]
sorter排序函数,本地排序使用一个函数(参考 Array.sort 的 compareFunction),需要服务端排序可设为 truefunction | boolean-
sortOrder排序的受控属性,外界可用此控制列的排序,可设置为 ascend descend falseboolean | string-
title列头显示文字(函数用法 3.10.0 后支持)ReactNode | ({ sortOrder, sortColumn, filters }) => ReactNode-
width列宽度(指定了也不生效?string | number-
onCell设置单元格属性function(record, rowIndex)-
onFilter本地模式下,确定筛选的运行函数function-
onFilterDropdownVisibleChange自定义筛选菜单可见变化时调用function(visible) {}-
onHeaderCell设置头部单元格属性function(column)-

ColumnGroup

参数说明类型默认值
title列头显示文字ReactNode-

pagination

分页的配置项。

参数说明类型默认值
position指定分页显示的位置, 取值为topLeft | topCenter | topRight |bottomLeft | bottomCenter | bottomRightArray[bottomRight]

更多配置项,请查看 Pagination

expandable

展开功能的配置。

参数说明类型默认值版本
childrenColumnName指定树形结构的列名stringchildren
columnWidth自定义展开列宽度string | number-
defaultExpandAllRows初始时,是否展开所有行booleanfalse
defaultExpandedRowKeys默认展开的行string[]-
expandedRowClassName展开行的 classNamefunction(record, index, indent): string-
expandedRowKeys展开的行,控制属性string[]-
expandedRowRender额外的展开行function(record, index, indent, expanded): ReactNode-
expandIcon自定义展开图标,参考示例function(props): ReactNode-
expandIconColumnIndex自定义展开按钮的列顺序,-1 时不展示number-
expandRowByClick通过点击行来展开子行booleanfalse
fixed控制展开图标是否固定,可选 true left rightboolean | stringfalse4.16.0
indentSize展示树形数据时,每层缩进的宽度,以 px 为单位number15
rowExpandable设置是否允许行展开(record) => boolean-
onExpand点击展开图标时触发function(expanded, record)-
onExpandedRowsChange展开的行变化时触发function(expandedRows)-
  • fixed

    • 当设置为 true 或 leftexpandIconColumnIndex 未设置或为 0 时,开启固定

    • 当设置为 true 或 rightexpandIconColumnIndex 设置为表格列数时,开启固定

rowSelection

选择功能的配置。

参数说明类型默认值版本
checkStrictlycheckable 状态下节点选择完全受控(父子数据选中状态不再关联)booleantrue4.4.0
columnTitle自定义列表选择框标题ReactNode-
columnWidth自定义列表选择框宽度string | number32px
fixed把选择框列固定在左边boolean-
getCheckboxProps选择框的默认属性配置function(record)-
hideSelectAll隐藏全选勾选框与自定义选择项booleanfalse4.3.0
preserveSelectedRowKeys当数据被删除时仍然保留选项的 keyboolean-4.4.0
renderCell渲染勾选框,用法与 Column 的 render 相同function(checked, record, index, originNode) {}-4.1.0
selectedRowKeys指定选中项的 key 数组,需要和 onChange 进行配合string[] | number[][]
defaultSelectedRowKeys默认选中项的 key 数组string[] | number[][]
selections自定义选择项 配置项, 设为 true 时使用默认选择项object[] | booleantrue
type多选/单选checkbox | radiocheckbox
onChange选中项发生变化时的回调function(selectedRowKeys, selectedRows)-
onSelect用户手动选择/取消选择某行的回调function(record, selected, selectedRows, nativeEvent)-
onSelectAll用户手动选择/取消选择所有行的回调function(selected, selectedRows, changeRows)-
onSelectInvert用户手动选择反选的回调function(selectedRowKeys)-
onSelectNone用户清空选择的回调function()-

scroll

参数说明类型默认值
scrollToFirstRowOnChange当分页、排序、筛选变化后是否滚动到表格顶部boolean-
x设置横向滚动,也可用于指定滚动区域的宽,可以设置为像素值,百分比,true 和 ‘max-content’string | number | true-
y设置纵向滚动,也可用于指定滚动区域的高,可以设置为像素值string | number-

selection

参数说明类型默认值
keyReact 需要的 key,建议设置string-
text选择项显示的文字ReactNode-
onSelect选择项点击回调function(changeableRowKeys)-

在 TypeScript 中使用

  1. import { Table } from 'antd';
  2. import { ColumnsType } from 'antd/es/table';
  3. interface User {
  4. key: number;
  5. name: string;
  6. }
  7. const columns: ColumnsType<User> = [
  8. {
  9. key: 'name',
  10. title: 'Name',
  11. dataIndex: 'name',
  12. },
  13. ];
  14. const data: User[] = [
  15. {
  16. key: 0,
  17. name: 'Jack',
  18. },
  19. ];
  20. export default () => (
  21. <>
  22. <Table<User> columns={columns} dataSource={data} />
  23. /* 使用 JSX 风格的 API */
  24. <Table<User> dataSource={data}>
  25. <Table.Column<User> key="name" title="Name" dataIndex="name" />
  26. </Table>
  27. </>
  28. );

TypeScript 里使用 Table 的 CodeSandbox 实例

注意

按照 React 的规范,所有的数组组件必须绑定 key。在 Table 中,dataSourcecolumns 里的数据值都需要指定 key 值。对于 dataSource 默认将每列数据的 key 属性作为唯一的标识。

控制台警告

如果 dataSource[i].key 没有提供,你应该使用 rowKey 来指定 dataSource 的主键,如下所示。若没有指定,控制台会出现以上的提示,表格组件也会出现各类奇怪的错误。

  1. // 比如你的数据主键是 uid
  2. return <Table rowKey="uid" />;
  3. // 或
  4. return <Table rowKey={record => record.uid} />;

从 v3 升级到 v4

Table 移除了在 v3 中废弃的 onRowClickonRowDoubleClickonRowMouseEnteronRowMouseLeave 等方法。如果你使用的 api 为文档中列举的 api,那你不用担心会丢失功能。

此外,比较重大的改动为 dataIndex 从支持路径嵌套如 user.age 改成了数组路径如 ['user', 'age']。以解决过去属性名带 . 需要额外的数据转化问题。

FAQ

如何在没有数据或只有一页数据时隐藏分页栏

你可以设置 paginationhideOnSinglePage 属性为 true

表格过滤时会回到第一页?

前端过滤时通常条目总数会减少,从而导致总页数小于筛选前的当前页数,为了防止当前页面没有数据,我们默认会返回第一页。

如果你在使用远程分页,很可能需要保持当前页面,你可以参照这个 受控例子 控制当前页面不变。

表格分页为何会出现 size 切换器?

4.1.0 起,Pagination 在 total 大于 50 条时会默认显示 size 切换器以提升用户交互体验。如果你不需要该功能,可以通过设置 showSizeChangerfalse 来关闭。

为什么 更新 state 会导致全表渲染?

由于 columns 支持 render 方法,因而 Table 无法知道哪些单元会受到影响。你可以通过 column.shouldCellUpdate 来控制单元格的渲染。

固定列穿透到最上层该怎么办?

固定列通过 z-index 属性将其悬浮于非固定列之上,这使得有时候你会发现在 Table 上放置遮罩层时固定列会被透过的情况。为遮罩层设置更高的 z-index 覆盖住固定列即可。