Table表格
展示行列数据。
设计师专属
安装 Kitchen Sketch 插件 💎,两步就可以自动生成 Ant Design 表格组件。
何时使用
当有大量结构化的数据需要展现时;
当需要对数据进行排序、搜索、分页、自定义操作等复杂行为时。
如何使用
指定表格的数据源 dataSource
为一个数组。
const dataSource = [
{
key: '1',
name: '胡彦斌',
age: 32,
address: '西湖区湖底公园1号',
},
{
key: '2',
name: '胡彦祖',
age: 42,
address: '西湖区湖底公园1号',
},
];
const columns = [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
},
{
title: '年龄',
dataIndex: 'age',
key: 'age',
},
{
title: '住址',
dataIndex: 'address',
key: 'address',
},
];
<Table dataSource={dataSource} columns={columns} />;
代码演示
简单的表格,最后一列是各种操作。
import { Table, Tag, Space } from 'antd';
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
render: text => <a>{text}</a>,
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
},
{
title: 'Tags',
key: 'tags',
dataIndex: 'tags',
render: tags => (
<>
{tags.map(tag => {
let color = tag.length > 5 ? 'geekblue' : 'green';
if (tag === 'loser') {
color = 'volcano';
}
return (
<Tag color={color} key={tag}>
{tag.toUpperCase()}
</Tag>
);
})}
</>
),
},
{
title: 'Action',
key: 'action',
render: (text, record) => (
<Space size="middle">
<a>Invite {record.name}</a>
<a>Delete</a>
</Space>
),
},
];
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
tags: ['nice', 'developer'],
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
tags: ['loser'],
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
tags: ['cool', 'teacher'],
},
];
ReactDOM.render(<Table columns={columns} dataSource={data} />, mountNode);
使用 JSX 风格的 API(2.5.0 以后引入)
这个只是一个描述
columns
的语法糖,所以你不能用其他组件去包裹Column
和ColumnGroup
。
import { Table, Tag, Space } from 'antd';
const { Column, ColumnGroup } = Table;
const data = [
{
key: '1',
firstName: 'John',
lastName: 'Brown',
age: 32,
address: 'New York No. 1 Lake Park',
tags: ['nice', 'developer'],
},
{
key: '2',
firstName: 'Jim',
lastName: 'Green',
age: 42,
address: 'London No. 1 Lake Park',
tags: ['loser'],
},
{
key: '3',
firstName: 'Joe',
lastName: 'Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
tags: ['cool', 'teacher'],
},
];
ReactDOM.render(
<Table dataSource={data}>
<ColumnGroup title="Name">
<Column title="First Name" dataIndex="firstName" key="firstName" />
<Column title="Last Name" dataIndex="lastName" key="lastName" />
</ColumnGroup>
<Column title="Age" dataIndex="age" key="age" />
<Column title="Address" dataIndex="address" key="address" />
<Column
title="Tags"
dataIndex="tags"
key="tags"
render={tags => (
<>
{tags.map(tag => (
<Tag color="blue" key={tag}>
{tag}
</Tag>
))}
</>
)}
/>
<Column
title="Action"
key="action"
render={(text, record) => (
<Space size="middle">
<a>Invite {record.lastName}</a>
<a>Delete</a>
</Space>
)}
/>
</Table>,
mountNode,
);
第一列是联动的选择框。可以通过 rowSelection.type
属性指定选择类型,默认为 checkbox
。
默认点击 checkbox 触发选择行为,需要点击行触发可以参考例子:https://codesandbox.io/s/000vqw38rl
TypeScript
JavaScript
import React, { useState } from 'react';
import { Table, Radio, Divider } from 'antd';
const columns = [
{
title: 'Name',
dataIndex: 'name',
render: (text: string) => <a>{text}</a>,
},
{
title: 'Age',
dataIndex: 'age',
},
{
title: 'Address',
dataIndex: 'address',
},
];
interface DataType {
key: React.Key;
name: string;
age: number;
address: string;
}
const data: DataType[] = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
{
key: '4',
name: 'Disabled User',
age: 99,
address: 'Sidney No. 1 Lake Park',
},
];
// rowSelection object indicates the need for row selection
const rowSelection = {
onChange: (selectedRowKeys: React.Key[], selectedRows: DataType[]) => {
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
},
getCheckboxProps: (record: DataType) => ({
disabled: record.name === 'Disabled User', // Column configuration not to be checked
name: record.name,
}),
};
const Demo = () => {
const [selectionType, setSelectionType] = useState<'checkbox' | 'radio'>('checkbox');
return (
<div>
<Radio.Group
onChange={({ target: { value } }) => {
setSelectionType(value);
}}
value={selectionType}
>
<Radio value="checkbox">Checkbox</Radio>
<Radio value="radio">radio</Radio>
</Radio.Group>
<Divider />
<Table
rowSelection={{
type: selectionType,
...rowSelection,
}}
columns={columns}
dataSource={data}
/>
</div>
);
};
ReactDOM.render(<Demo />, mountNode);
选择后进行操作,完成后清空选择,通过 rowSelection.selectedRowKeys
来控制选中项。
import { Table, Button } from 'antd';
const columns = [
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Age',
dataIndex: 'age',
},
{
title: 'Address',
dataIndex: 'address',
},
];
const data = [];
for (let i = 0; i < 46; i++) {
data.push({
key: i,
name: `Edward King ${i}`,
age: 32,
address: `London, Park Lane no. ${i}`,
});
}
class App extends React.Component {
state = {
selectedRowKeys: [], // Check here to configure the default column
loading: false,
};
start = () => {
this.setState({ loading: true });
// ajax request after empty completing
setTimeout(() => {
this.setState({
selectedRowKeys: [],
loading: false,
});
}, 1000);
};
onSelectChange = selectedRowKeys => {
console.log('selectedRowKeys changed: ', selectedRowKeys);
this.setState({ selectedRowKeys });
};
render() {
const { loading, selectedRowKeys } = this.state;
const rowSelection = {
selectedRowKeys,
onChange: this.onSelectChange,
};
const hasSelected = selectedRowKeys.length > 0;
return (
<div>
<div style={{ marginBottom: 16 }}>
<Button type="primary" onClick={this.start} disabled={!hasSelected} loading={loading}>
Reload
</Button>
<span style={{ marginLeft: 8 }}>
{hasSelected ? `Selected ${selectedRowKeys.length} items` : ''}
</span>
</div>
<Table rowSelection={rowSelection} columns={columns} dataSource={data} />
</div>
);
}
}
ReactDOM.render(<App />, mountNode);
通过 rowSelection.selections
自定义选择项,默认不显示下拉选项,设为 true
时显示默认选择项。
import { Table } from 'antd';
const columns = [
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Age',
dataIndex: 'age',
},
{
title: 'Address',
dataIndex: 'address',
},
];
const data = [];
for (let i = 0; i < 46; i++) {
data.push({
key: i,
name: `Edward King ${i}`,
age: 32,
address: `London, Park Lane no. ${i}`,
});
}
class App extends React.Component {
state = {
selectedRowKeys: [], // Check here to configure the default column
};
onSelectChange = selectedRowKeys => {
console.log('selectedRowKeys changed: ', selectedRowKeys);
this.setState({ selectedRowKeys });
};
render() {
const { selectedRowKeys } = this.state;
const rowSelection = {
selectedRowKeys,
onChange: this.onSelectChange,
selections: [
Table.SELECTION_ALL,
Table.SELECTION_INVERT,
Table.SELECTION_NONE,
{
key: 'odd',
text: 'Select Odd Row',
onSelect: changableRowKeys => {
let newSelectedRowKeys = [];
newSelectedRowKeys = changableRowKeys.filter((key, index) => {
if (index % 2 !== 0) {
return false;
}
return true;
});
this.setState({ selectedRowKeys: newSelectedRowKeys });
},
},
{
key: 'even',
text: 'Select Even Row',
onSelect: changableRowKeys => {
let newSelectedRowKeys = [];
newSelectedRowKeys = changableRowKeys.filter((key, index) => {
if (index % 2 !== 0) {
return true;
}
return false;
});
this.setState({ selectedRowKeys: newSelectedRowKeys });
},
},
],
};
return <Table rowSelection={rowSelection} columns={columns} dataSource={data} />;
}
}
ReactDOM.render(<App />, mountNode);
对某一列数据进行筛选,使用列的 filters
属性来指定需要筛选菜单的列,onFilter
用于筛选当前数据,filterMultiple
用于指定多选和单选。
对某一列数据进行排序,通过指定列的 sorter
函数即可启动排序按钮。sorter: function(rowA, rowB) { ... }
, rowA、rowB 为比较的两个行数据。
sortDirections: ['ascend' | 'descend']
改变每列可用的排序方式,切换排序时按数组内容依次切换,设置在 table props 上时对所有列生效。你可以通过设置 ['ascend', 'descend', 'ascend']
禁止排序恢复到默认状态。
使用 defaultSortOrder
属性,设置列的默认排序顺序。
import { Table } from 'antd';
const columns = [
{
title: 'Name',
dataIndex: 'name',
filters: [
{
text: 'Joe',
value: 'Joe',
},
{
text: 'Jim',
value: 'Jim',
},
{
text: 'Submenu',
value: 'Submenu',
children: [
{
text: 'Green',
value: 'Green',
},
{
text: 'Black',
value: 'Black',
},
],
},
],
// specify the condition of filtering result
// here is that finding the name started with `value`
onFilter: (value, record) => record.name.indexOf(value) === 0,
sorter: (a, b) => a.name.length - b.name.length,
sortDirections: ['descend'],
},
{
title: 'Age',
dataIndex: 'age',
defaultSortOrder: 'descend',
sorter: (a, b) => a.age - b.age,
},
{
title: 'Address',
dataIndex: 'address',
filters: [
{
text: 'London',
value: 'London',
},
{
text: 'New York',
value: 'New York',
},
],
onFilter: (value, record) => record.address.indexOf(value) === 0,
},
];
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
{
key: '4',
name: 'Jim Red',
age: 32,
address: 'London No. 2 Lake Park',
},
];
function onChange(pagination, filters, sorter, extra) {
console.log('params', pagination, filters, sorter, extra);
}
ReactDOM.render(<Table columns={columns} dataSource={data} onChange={onChange} />, mountNode);
column.sorter
支持 multiple
字段以配置多列排序优先级。通过 sorter.compare
配置排序逻辑,你可以通过不设置该函数只启动多列排序的交互形式。
import { Table } from 'antd';
const columns = [
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Chinese Score',
dataIndex: 'chinese',
sorter: {
compare: (a, b) => a.chinese - b.chinese,
multiple: 3,
},
},
{
title: 'Math Score',
dataIndex: 'math',
sorter: {
compare: (a, b) => a.math - b.math,
multiple: 2,
},
},
{
title: 'English Score',
dataIndex: 'english',
sorter: {
compare: (a, b) => a.english - b.english,
multiple: 1,
},
},
];
const data = [
{
key: '1',
name: 'John Brown',
chinese: 98,
math: 60,
english: 70,
},
{
key: '2',
name: 'Jim Green',
chinese: 98,
math: 66,
english: 89,
},
{
key: '3',
name: 'Joe Black',
chinese: 98,
math: 90,
english: 70,
},
{
key: '4',
name: 'Jim Red',
chinese: 88,
math: 99,
english: 89,
},
];
function onChange(pagination, filters, sorter, extra) {
console.log('params', pagination, filters, sorter, extra);
}
ReactDOM.render(<Table columns={columns} dataSource={data} onChange={onChange} />, mountNode);
使用受控属性对筛选和排序状态进行控制。
columns 中定义了 filteredValue 和 sortOrder 属性即视为受控模式。
只支持同时对一列进行排序,请保证只有一列的 sortOrder 属性是生效的。
务必指定
column.key
。
import { Table, Button, Space } from 'antd';
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
{
key: '4',
name: 'Jim Red',
age: 32,
address: 'London No. 2 Lake Park',
},
];
class App extends React.Component {
state = {
filteredInfo: null,
sortedInfo: null,
};
handleChange = (pagination, filters, sorter) => {
console.log('Various parameters', pagination, filters, sorter);
this.setState({
filteredInfo: filters,
sortedInfo: sorter,
});
};
clearFilters = () => {
this.setState({ filteredInfo: null });
};
clearAll = () => {
this.setState({
filteredInfo: null,
sortedInfo: null,
});
};
setAgeSort = () => {
this.setState({
sortedInfo: {
order: 'descend',
columnKey: 'age',
},
});
};
render() {
let { sortedInfo, filteredInfo } = this.state;
sortedInfo = sortedInfo || {};
filteredInfo = filteredInfo || {};
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
filters: [
{ text: 'Joe', value: 'Joe' },
{ text: 'Jim', value: 'Jim' },
],
filteredValue: filteredInfo.name || null,
onFilter: (value, record) => record.name.includes(value),
sorter: (a, b) => a.name.length - b.name.length,
sortOrder: sortedInfo.columnKey === 'name' && sortedInfo.order,
ellipsis: true,
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
sorter: (a, b) => a.age - b.age,
sortOrder: sortedInfo.columnKey === 'age' && sortedInfo.order,
ellipsis: true,
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
filters: [
{ text: 'London', value: 'London' },
{ text: 'New York', value: 'New York' },
],
filteredValue: filteredInfo.address || null,
onFilter: (value, record) => record.address.includes(value),
sorter: (a, b) => a.address.length - b.address.length,
sortOrder: sortedInfo.columnKey === 'address' && sortedInfo.order,
ellipsis: true,
},
];
return (
<>
<Space style={{ marginBottom: 16 }}>
<Button onClick={this.setAgeSort}>Sort age</Button>
<Button onClick={this.clearFilters}>Clear filters</Button>
<Button onClick={this.clearAll}>Clear filters and sorters</Button>
</Space>
<Table columns={columns} dataSource={data} onChange={this.handleChange} />
</>
);
}
}
ReactDOM.render(<App />, mountNode);
通过 filterDropdown
自定义的列筛选功能,并实现一个搜索列的示例。
给函数 confirm
添加 boolean
类型参数 closeDropdown
,是否关闭筛选菜单,默认为 true
。
import { Table, Input, Button, Space } from 'antd';
import Highlighter from 'react-highlight-words';
import { SearchOutlined } from '@ant-design/icons';
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
},
{
key: '2',
name: 'Joe Black',
age: 42,
address: 'London No. 1 Lake Park',
},
{
key: '3',
name: 'Jim Green',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
{
key: '4',
name: 'Jim Red',
age: 32,
address: 'London No. 2 Lake Park',
},
];
class App extends React.Component {
state = {
searchText: '',
searchedColumn: '',
};
getColumnSearchProps = dataIndex => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
<div style={{ padding: 8 }}>
<Input
ref={node => {
this.searchInput = node;
}}
placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
style={{ marginBottom: 8, display: 'block' }}
/>
<Space>
<Button
type="primary"
onClick={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
icon={<SearchOutlined />}
size="small"
style={{ width: 90 }}
>
Search
</Button>
<Button onClick={() => this.handleReset(clearFilters)} size="small" style={{ width: 90 }}>
Reset
</Button>
<Button
type="link"
size="small"
onClick={() => {
confirm({ closeDropdown: false });
this.setState({
searchText: selectedKeys[0],
searchedColumn: dataIndex,
});
}}
>
Filter
</Button>
</Space>
</div>
),
filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
onFilter: (value, record) =>
record[dataIndex]
? record[dataIndex].toString().toLowerCase().includes(value.toLowerCase())
: '',
onFilterDropdownVisibleChange: visible => {
if (visible) {
setTimeout(() => this.searchInput.select(), 100);
}
},
render: text =>
this.state.searchedColumn === dataIndex ? (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={[this.state.searchText]}
autoEscape
textToHighlight={text ? text.toString() : ''}
/>
) : (
text
),
});
handleSearch = (selectedKeys, confirm, dataIndex) => {
confirm();
this.setState({
searchText: selectedKeys[0],
searchedColumn: dataIndex,
});
};
handleReset = clearFilters => {
clearFilters();
this.setState({ searchText: '' });
};
render() {
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
width: '30%',
...this.getColumnSearchProps('name'),
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
width: '20%',
...this.getColumnSearchProps('age'),
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
...this.getColumnSearchProps('address'),
sorter: (a, b) => a.address.length - b.address.length,
sortDirections: ['descend', 'ascend'],
},
];
return <Table columns={columns} dataSource={data} />;
}
}
ReactDOM.render(<App />, mountNode);
这个例子通过简单的 ajax 读取方式,演示了如何从服务端读取并展现数据,具有筛选、排序等功能以及页面 loading 效果。开发者可以自行接入其他数据处理方式。
另外,本例也展示了筛选排序功能如何交给服务端实现,列不需要指定具体的 onFilter
和 sorter
函数,而是在把筛选和排序的参数发到服务端来处理。
当使用 rowSelection
时,请设置 rowSelection.preserveSelectedRowKeys
属性以保留 key
。
注意,此示例使用 模拟接口,展示数据可能不准确,请打开网络面板查看请求。
🛎️ 想要 3 分钟实现?试试 ProTable!
import { Table } from 'antd';
import reqwest from 'reqwest';
const columns = [
{
title: 'Name',
dataIndex: 'name',
sorter: true,
render: name => `${name.first} ${name.last}`,
width: '20%',
},
{
title: 'Gender',
dataIndex: 'gender',
filters: [
{ text: 'Male', value: 'male' },
{ text: 'Female', value: 'female' },
],
width: '20%',
},
{
title: 'Email',
dataIndex: 'email',
},
];
const getRandomuserParams = params => ({
results: params.pagination.pageSize,
page: params.pagination.current,
...params,
});
class App extends React.Component {
state = {
data: [],
pagination: {
current: 1,
pageSize: 10,
},
loading: false,
};
componentDidMount() {
const { pagination } = this.state;
this.fetch({ pagination });
}
handleTableChange = (pagination, filters, sorter) => {
this.fetch({
sortField: sorter.field,
sortOrder: sorter.order,
pagination,
...filters,
});
};
fetch = (params = {}) => {
this.setState({ loading: true });
reqwest({
url: 'https://randomuser.me/api',
method: 'get',
type: 'json',
data: getRandomuserParams(params),
}).then(data => {
console.log(data);
this.setState({
loading: false,
data: data.results,
pagination: {
...params.pagination,
total: 200,
// 200 is mock data, you should read it from server
// total: data.totalCount,
},
});
});
};
render() {
const { data, pagination, loading } = this.state;
return (
<Table
columns={columns}
rowKey={record => record.login.uuid}
dataSource={data}
pagination={pagination}
loading={loading}
onChange={this.handleTableChange}
/>
);
}
}
ReactDOM.render(<App />, mountNode);
两种紧凑型的列表,小型列表只用于对话框内。
import { Table } from 'antd';
const columns = [
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Age',
dataIndex: 'age',
},
{
title: 'Address',
dataIndex: 'address',
},
];
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
];
ReactDOM.render(
<div>
<h4>Middle size table</h4>
<Table columns={columns} dataSource={data} size="middle" />
<h4>Small size table</h4>
<Table columns={columns} dataSource={data} size="small" />
</div>,
mountNode,
);
添加表格边框线,页头和页脚。
import { Table } from 'antd';
const columns = [
{
title: 'Name',
dataIndex: 'name',
render: text => <a>{text}</a>,
},
{
title: 'Cash Assets',
className: 'column-money',
dataIndex: 'money',
align: 'right',
},
{
title: 'Address',
dataIndex: 'address',
},
];
const data = [
{
key: '1',
name: 'John Brown',
money: '¥300,000.00',
address: 'New York No. 1 Lake Park',
},
{
key: '2',
name: 'Jim Green',
money: '¥1,256,000.00',
address: 'London No. 1 Lake Park',
},
{
key: '3',
name: 'Joe Black',
money: '¥120,000.00',
address: 'Sidney No. 1 Lake Park',
},
];
ReactDOM.render(
<Table
columns={columns}
dataSource={data}
bordered
title={() => 'Header'}
footer={() => 'Footer'}
/>,
mountNode,
);
当表格内容较多不能一次性完全展示时。
import { Table } from 'antd';
const columns = [
{ title: 'Name', dataIndex: 'name', key: 'name' },
{ title: 'Age', dataIndex: 'age', key: 'age' },
{ title: 'Address', dataIndex: 'address', key: 'address' },
{
title: 'Action',
dataIndex: '',
key: 'x',
render: () => <a>Delete</a>,
},
];
const data = [
{
key: 1,
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
description: 'My name is John Brown, I am 32 years old, living in New York No. 1 Lake Park.',
},
{
key: 2,
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
description: 'My name is Jim Green, I am 42 years old, living in London No. 1 Lake Park.',
},
{
key: 3,
name: 'Not Expandable',
age: 29,
address: 'Jiangsu No. 1 Lake Park',
description: 'This not expandable',
},
{
key: 4,
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
description: 'My name is Joe Black, I am 32 years old, living in Sidney No. 1 Lake Park.',
},
];
ReactDOM.render(
<Table
columns={columns}
expandable={{
expandedRowRender: record => <p style={{ margin: 0 }}>{record.description}</p>,
rowExpandable: record => record.name !== 'Not Expandable',
}}
dataSource={data}
/>,
mountNode,
);
表头只支持列合并,使用 column 里的 colSpan 进行设置。
表格支持行/列合并,使用 render 里的单元格属性 colSpan 或者 rowSpan 设值为 0 时,设置的表格不会渲染。
import { Table } from 'antd';
// In the fifth row, other columns are merged into first column
// by setting it's colSpan to be 0
const renderContent = (value, row, index) => {
const obj = {
children: value,
props: {},
};
if (index === 4) {
obj.props.colSpan = 0;
}
return obj;
};
const columns = [
{
title: 'Name',
dataIndex: 'name',
render: (text, row, index) => {
if (index < 4) {
return <a>{text}</a>;
}
return {
children: <a>{text}</a>,
props: {
colSpan: 5,
},
};
},
},
{
title: 'Age',
dataIndex: 'age',
render: renderContent,
},
{
title: 'Home phone',
colSpan: 2,
dataIndex: 'tel',
render: (value, row, index) => {
const obj = {
children: value,
props: {},
};
if (index === 2) {
obj.props.rowSpan = 2;
}
// These two are merged into above cell
if (index === 3) {
obj.props.rowSpan = 0;
}
if (index === 4) {
obj.props.colSpan = 0;
}
return obj;
},
},
{
title: 'Phone',
colSpan: 0,
dataIndex: 'phone',
render: renderContent,
},
{
title: 'Address',
dataIndex: 'address',
render: renderContent,
},
];
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
tel: '0571-22098909',
phone: 18889898989,
address: 'New York No. 1 Lake Park',
},
{
key: '2',
name: 'Jim Green',
tel: '0571-22098333',
phone: 18889898888,
age: 42,
address: 'London No. 1 Lake Park',
},
{
key: '3',
name: 'Joe Black',
age: 32,
tel: '0575-22098909',
phone: 18900010002,
address: 'Sidney No. 1 Lake Park',
},
{
key: '4',
name: 'Jim Red',
age: 18,
tel: '0575-22098909',
phone: 18900010002,
address: 'London No. 2 Lake Park',
},
{
key: '5',
name: 'Jake White',
age: 18,
tel: '0575-22098909',
phone: 18900010002,
address: 'Dublin No. 2 Lake Park',
},
];
ReactDOM.render(<Table columns={columns} dataSource={data} bordered />, mountNode);
表格支持树形数据的展示,当数据中有 children
字段时会自动展示为树形表格,如果不需要或配置为其他字段可以用 childrenColumnName
进行配置。
可以通过设置 indentSize
以控制每一层的缩进宽度。
import { Table, Switch, Space } from 'antd';
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
width: '12%',
},
{
title: 'Address',
dataIndex: 'address',
width: '30%',
key: 'address',
},
];
const data = [
{
key: 1,
name: 'John Brown sr.',
age: 60,
address: 'New York No. 1 Lake Park',
children: [
{
key: 11,
name: 'John Brown',
age: 42,
address: 'New York No. 2 Lake Park',
},
{
key: 12,
name: 'John Brown jr.',
age: 30,
address: 'New York No. 3 Lake Park',
children: [
{
key: 121,
name: 'Jimmy Brown',
age: 16,
address: 'New York No. 3 Lake Park',
},
],
},
{
key: 13,
name: 'Jim Green sr.',
age: 72,
address: 'London No. 1 Lake Park',
children: [
{
key: 131,
name: 'Jim Green',
age: 42,
address: 'London No. 2 Lake Park',
children: [
{
key: 1311,
name: 'Jim Green jr.',
age: 25,
address: 'London No. 3 Lake Park',
},
{
key: 1312,
name: 'Jimmy Green sr.',
age: 18,
address: 'London No. 4 Lake Park',
},
],
},
],
},
],
},
{
key: 2,
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
];
// rowSelection objects indicates the need for row selection
const rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
},
onSelect: (record, selected, selectedRows) => {
console.log(record, selected, selectedRows);
},
onSelectAll: (selected, selectedRows, changeRows) => {
console.log(selected, selectedRows, changeRows);
},
};
function TreeData() {
const [checkStrictly, setCheckStrictly] = React.useState(false);
return (
<>
<Space align="center" style={{ marginBottom: 16 }}>
CheckStrictly: <Switch checked={checkStrictly} onChange={setCheckStrictly} />
</Space>
<Table
columns={columns}
rowSelection={{ ...rowSelection, checkStrictly }}
dataSource={data}
/>
</>
);
}
ReactDOM.render(<TreeData />, mountNode);
方便一页内展示大量数据。
需要指定 column 的 width
属性,否则列头和内容可能不对齐。如果指定 width
不生效或出现白色垂直空隙,请尝试建议留一列不设宽度以适应弹性布局,或者检查是否有超长连续字段破坏布局。
import { Table } from 'antd';
const columns = [
{
title: 'Name',
dataIndex: 'name',
width: 150,
},
{
title: 'Age',
dataIndex: 'age',
width: 150,
},
{
title: 'Address',
dataIndex: 'address',
},
];
const data = [];
for (let i = 0; i < 100; i++) {
data.push({
key: i,
name: `Edward King ${i}`,
age: 32,
address: `London, Park Lane no. ${i}`,
});
}
ReactDOM.render(
<Table columns={columns} dataSource={data} pagination={{ pageSize: 50 }} scroll={{ y: 240 }} />,
mountNode,
);
对于列数很多的数据,可以固定前后的列,横向滚动查看其它数据,需要和 scroll.x
配合使用。
若列头与内容不对齐或出现列重复,请指定固定列的宽度
width
。如果指定width
不生效或出现白色垂直空隙,请尝试建议留一列不设宽度以适应弹性布局,或者检查是否有超长连续字段破坏布局。建议指定
scroll.x
为大于表格宽度的固定值或百分比。注意,且非固定列宽度之和不要超过scroll.x
。
注意:v4 版本固定列通过 sticky 实现,IE 11 会降级成横向滚动。
import { Table } from 'antd';
const columns = [
{
title: 'Full Name',
width: 100,
dataIndex: 'name',
key: 'name',
fixed: 'left',
},
{
title: 'Age',
width: 100,
dataIndex: 'age',
key: 'age',
fixed: 'left',
},
{ title: 'Column 1', dataIndex: 'address', key: '1' },
{ title: 'Column 2', dataIndex: 'address', key: '2' },
{ title: 'Column 3', dataIndex: 'address', key: '3' },
{ title: 'Column 4', dataIndex: 'address', key: '4' },
{ title: 'Column 5', dataIndex: 'address', key: '5' },
{ title: 'Column 6', dataIndex: 'address', key: '6' },
{ title: 'Column 7', dataIndex: 'address', key: '7' },
{ title: 'Column 8', dataIndex: 'address', key: '8' },
{
title: 'Action',
key: 'operation',
fixed: 'right',
width: 100,
render: () => <a>action</a>,
},
];
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York Park',
},
{
key: '2',
name: 'Jim Green',
age: 40,
address: 'London Park',
},
];
ReactDOM.render(<Table columns={columns} dataSource={data} scroll={{ x: 1300 }} />, mountNode);
适合同时展示有大量数据和数据列。
若列头与内容不对齐或出现列重复,请指定固定列的宽度
width
。如果指定width
不生效或出现白色垂直空隙,请尝试建议留一列不设宽度以适应弹性布局,或者检查是否有超长连续字段破坏布局。建议指定
scroll.x
为大于表格宽度的固定值或百分比。注意,且非固定列宽度之和不要超过scroll.x
。
import { Table } from 'antd';
const columns = [
{
title: 'Full Name',
width: 100,
dataIndex: 'name',
key: 'name',
fixed: 'left',
},
{
title: 'Age',
width: 100,
dataIndex: 'age',
key: 'age',
fixed: 'left',
},
{
title: 'Column 1',
dataIndex: 'address',
key: '1',
width: 150,
},
{
title: 'Column 2',
dataIndex: 'address',
key: '2',
width: 150,
},
{
title: 'Column 3',
dataIndex: 'address',
key: '3',
width: 150,
},
{
title: 'Column 4',
dataIndex: 'address',
key: '4',
width: 150,
},
{
title: 'Column 5',
dataIndex: 'address',
key: '5',
width: 150,
},
{
title: 'Column 6',
dataIndex: 'address',
key: '6',
width: 150,
},
{
title: 'Column 7',
dataIndex: 'address',
key: '7',
width: 150,
},
{ title: 'Column 8', dataIndex: 'address', key: '8' },
{
title: 'Action',
key: 'operation',
fixed: 'right',
width: 100,
render: () => <a>action</a>,
},
];
const data = [];
for (let i = 0; i < 100; i++) {
data.push({
key: i,
name: `Edrward ${i}`,
age: 32,
address: `London Park no. ${i}`,
});
}
ReactDOM.render(
<Table columns={columns} dataSource={data} scroll={{ x: 1500, y: 300 }} />,
mountNode,
);
columns[n]
可以内嵌 children
,以渲染分组表头。
import { Table } from 'antd';
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
width: 100,
fixed: 'left',
filters: [
{
text: 'Joe',
value: 'Joe',
},
{
text: 'John',
value: 'John',
},
],
onFilter: (value, record) => record.name.indexOf(value) === 0,
},
{
title: 'Other',
children: [
{
title: 'Age',
dataIndex: 'age',
key: 'age',
width: 150,
sorter: (a, b) => a.age - b.age,
},
{
title: 'Address',
children: [
{
title: 'Street',
dataIndex: 'street',
key: 'street',
width: 150,
},
{
title: 'Block',
children: [
{
title: 'Building',
dataIndex: 'building',
key: 'building',
width: 100,
},
{
title: 'Door No.',
dataIndex: 'number',
key: 'number',
width: 100,
},
],
},
],
},
],
},
{
title: 'Company',
children: [
{
title: 'Company Address',
dataIndex: 'companyAddress',
key: 'companyAddress',
width: 200,
},
{
title: 'Company Name',
dataIndex: 'companyName',
key: 'companyName',
},
],
},
{
title: 'Gender',
dataIndex: 'gender',
key: 'gender',
width: 80,
fixed: 'right',
},
];
const data = [];
for (let i = 0; i < 100; i++) {
data.push({
key: i,
name: 'John Brown',
age: i + 1,
street: 'Lake Park',
building: 'C',
number: 2035,
companyAddress: 'Lake Street 42',
companyName: 'SoftLake Co',
gender: 'M',
});
}
ReactDOM.render(
<Table
columns={columns}
dataSource={data}
bordered
size="middle"
scroll={{ x: 'calc(700px + 50%)', y: 240 }}
/>,
mountNode,
);
带单元格编辑功能的表格。当配合 shouldCellUpdate
使用时请注意闭包问题。
TypeScript
JavaScript
import React, { useContext, useState, useEffect, useRef } from 'react';
import { Table, Input, Button, Popconfirm, Form } from 'antd';
import { FormInstance } from 'antd/lib/form';
const EditableContext = React.createContext<FormInstance<any> | null>(null);
interface Item {
key: string;
name: string;
age: string;
address: string;
}
interface EditableRowProps {
index: number;
}
const EditableRow: React.FC<EditableRowProps> = ({ index, ...props }) => {
const [form] = Form.useForm();
return (
<Form form={form} component={false}>
<EditableContext.Provider value={form}>
<tr {...props} />
</EditableContext.Provider>
</Form>
);
};
interface EditableCellProps {
title: React.ReactNode;
editable: boolean;
children: React.ReactNode;
dataIndex: keyof Item;
record: Item;
handleSave: (record: Item) => void;
}
const EditableCell: React.FC<EditableCellProps> = ({
title,
editable,
children,
dataIndex,
record,
handleSave,
...restProps
}) => {
const [editing, setEditing] = useState(false);
const inputRef = useRef<Input>(null);
const form = useContext(EditableContext)!;
useEffect(() => {
if (editing) {
inputRef.current!.focus();
}
}, [editing]);
const toggleEdit = () => {
setEditing(!editing);
form.setFieldsValue({ [dataIndex]: record[dataIndex] });
};
const save = async () => {
try {
const values = await form.validateFields();
toggleEdit();
handleSave({ ...record, ...values });
} catch (errInfo) {
console.log('Save failed:', errInfo);
}
};
let childNode = children;
if (editable) {
childNode = editing ? (
<Form.Item
style={{ margin: 0 }}
name={dataIndex}
rules={[
{
required: true,
message: `${title} is required.`,
},
]}
>
<Input ref={inputRef} onPressEnter={save} onBlur={save} />
</Form.Item>
) : (
<div className="editable-cell-value-wrap" style={{ paddingRight: 24 }} onClick={toggleEdit}>
{children}
</div>
);
}
return <td {...restProps}>{childNode}</td>;
};
type EditableTableProps = Parameters<typeof Table>[0];
interface DataType {
key: React.Key;
name: string;
age: string;
address: string;
}
interface EditableTableState {
dataSource: DataType[];
count: number;
}
type ColumnTypes = Exclude<EditableTableProps['columns'], undefined>;
class EditableTable extends React.Component<EditableTableProps, EditableTableState> {
columns: (ColumnTypes[number] & { editable?: boolean; dataIndex: string })[];
constructor(props: EditableTableProps) {
super(props);
this.columns = [
{
title: 'name',
dataIndex: 'name',
width: '30%',
editable: true,
},
{
title: 'age',
dataIndex: 'age',
},
{
title: 'address',
dataIndex: 'address',
},
{
title: 'operation',
dataIndex: 'operation',
render: (_, record: { key: React.Key }) =>
this.state.dataSource.length >= 1 ? (
<Popconfirm title="Sure to delete?" onConfirm={() => this.handleDelete(record.key)}>
<a>Delete</a>
</Popconfirm>
) : null,
},
];
this.state = {
dataSource: [
{
key: '0',
name: 'Edward King 0',
age: '32',
address: 'London, Park Lane no. 0',
},
{
key: '1',
name: 'Edward King 1',
age: '32',
address: 'London, Park Lane no. 1',
},
],
count: 2,
};
}
handleDelete = (key: React.Key) => {
const dataSource = [...this.state.dataSource];
this.setState({ dataSource: dataSource.filter(item => item.key !== key) });
};
handleAdd = () => {
const { count, dataSource } = this.state;
const newData: DataType = {
key: count,
name: `Edward King ${count}`,
age: '32',
address: `London, Park Lane no. ${count}`,
};
this.setState({
dataSource: [...dataSource, newData],
count: count + 1,
});
};
handleSave = (row: DataType) => {
const newData = [...this.state.dataSource];
const index = newData.findIndex(item => row.key === item.key);
const item = newData[index];
newData.splice(index, 1, {
...item,
...row,
});
this.setState({ dataSource: newData });
};
render() {
const { dataSource } = this.state;
const components = {
body: {
row: EditableRow,
cell: EditableCell,
},
};
const columns = this.columns.map(col => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record: DataType) => ({
record,
editable: col.editable,
dataIndex: col.dataIndex,
title: col.title,
handleSave: this.handleSave,
}),
};
});
return (
<div>
<Button onClick={this.handleAdd} type="primary" style={{ marginBottom: 16 }}>
Add a row
</Button>
<Table
components={components}
rowClassName={() => 'editable-row'}
bordered
dataSource={dataSource}
columns={columns as ColumnTypes}
/>
</div>
);
}
}
ReactDOM.render(<EditableTable />, mountNode);
.editable-cell {
position: relative;
}
.editable-cell-value-wrap {
padding: 5px 12px;
cursor: pointer;
}
.editable-row:hover .editable-cell-value-wrap {
padding: 4px 11px;
border: 1px solid #d9d9d9;
border-radius: 4px;
}
[data-theme='dark'] .editable-row:hover .editable-cell-value-wrap {
border: 1px solid #434343;
}
带行编辑功能的表格。
🛎️ 想要 3 分钟实现?试试 ProTable 的可编辑表格!
TypeScript
JavaScript
import React, { useState } from 'react';
import { Table, Input, InputNumber, Popconfirm, Form, Typography } from 'antd';
interface Item {
key: string;
name: string;
age: number;
address: string;
}
const originData: Item[] = [];
for (let i = 0; i < 100; i++) {
originData.push({
key: i.toString(),
name: `Edrward ${i}`,
age: 32,
address: `London Park no. ${i}`,
});
}
interface EditableCellProps extends React.HTMLAttributes<HTMLElement> {
editing: boolean;
dataIndex: string;
title: any;
inputType: 'number' | 'text';
record: Item;
index: number;
children: React.ReactNode;
}
const EditableCell: React.FC<EditableCellProps> = ({
editing,
dataIndex,
title,
inputType,
record,
index,
children,
...restProps
}) => {
const inputNode = inputType === 'number' ? <InputNumber /> : <Input />;
return (
<td {...restProps}>
{editing ? (
<Form.Item
name={dataIndex}
style={{ margin: 0 }}
rules={[
{
required: true,
message: `Please Input ${title}!`,
},
]}
>
{inputNode}
</Form.Item>
) : (
children
)}
</td>
);
};
const EditableTable = () => {
const [form] = Form.useForm();
const [data, setData] = useState(originData);
const [editingKey, setEditingKey] = useState('');
const isEditing = (record: Item) => record.key === editingKey;
const edit = (record: Partial<Item> & { key: React.Key }) => {
form.setFieldsValue({ name: '', age: '', address: '', ...record });
setEditingKey(record.key);
};
const cancel = () => {
setEditingKey('');
};
const save = async (key: React.Key) => {
try {
const row = (await form.validateFields()) as Item;
const newData = [...data];
const index = newData.findIndex(item => key === item.key);
if (index > -1) {
const item = newData[index];
newData.splice(index, 1, {
...item,
...row,
});
setData(newData);
setEditingKey('');
} else {
newData.push(row);
setData(newData);
setEditingKey('');
}
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
};
const columns = [
{
title: 'name',
dataIndex: 'name',
width: '25%',
editable: true,
},
{
title: 'age',
dataIndex: 'age',
width: '15%',
editable: true,
},
{
title: 'address',
dataIndex: 'address',
width: '40%',
editable: true,
},
{
title: 'operation',
dataIndex: 'operation',
render: (_: any, record: Item) => {
const editable = isEditing(record);
return editable ? (
<span>
<a href="javascript:;" onClick={() => save(record.key)} style={{ marginRight: 8 }}>
Save
</a>
<Popconfirm title="Sure to cancel?" onConfirm={cancel}>
<a>Cancel</a>
</Popconfirm>
</span>
) : (
<Typography.Link disabled={editingKey !== ''} onClick={() => edit(record)}>
Edit
</Typography.Link>
);
},
},
];
const mergedColumns = columns.map(col => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record: Item) => ({
record,
inputType: col.dataIndex === 'age' ? 'number' : 'text',
dataIndex: col.dataIndex,
title: col.title,
editing: isEditing(record),
}),
};
});
return (
<Form form={form} component={false}>
<Table
components={{
body: {
cell: EditableCell,
},
}}
bordered
dataSource={data}
columns={mergedColumns}
rowClassName="editable-row"
pagination={{
onChange: cancel,
}}
/>
</Form>
);
};
ReactDOM.render(<EditableTable />, mountNode);
.editable-row .ant-form-item-explain {
position: absolute;
top: 100%;
font-size: 12px;
}
展示每行数据更详细的信息。
import { Table, Badge, Menu, Dropdown, Space } from 'antd';
import { DownOutlined } from '@ant-design/icons';
const menu = (
<Menu>
<Menu.Item>Action 1</Menu.Item>
<Menu.Item>Action 2</Menu.Item>
</Menu>
);
function NestedTable() {
const expandedRowRender = () => {
const columns = [
{ title: 'Date', dataIndex: 'date', key: 'date' },
{ title: 'Name', dataIndex: 'name', key: 'name' },
{
title: 'Status',
key: 'state',
render: () => (
<span>
<Badge status="success" />
Finished
</span>
),
},
{ title: 'Upgrade Status', dataIndex: 'upgradeNum', key: 'upgradeNum' },
{
title: 'Action',
dataIndex: 'operation',
key: 'operation',
render: () => (
<Space size="middle">
<a>Pause</a>
<a>Stop</a>
<Dropdown overlay={menu}>
<a>
More <DownOutlined />
</a>
</Dropdown>
</Space>
),
},
];
const data = [];
for (let i = 0; i < 3; ++i) {
data.push({
key: i,
date: '2014-12-24 23:12:00',
name: 'This is production name',
upgradeNum: 'Upgraded: 56',
});
}
return <Table columns={columns} dataSource={data} pagination={false} />;
};
const columns = [
{ title: 'Name', dataIndex: 'name', key: 'name' },
{ title: 'Platform', dataIndex: 'platform', key: 'platform' },
{ title: 'Version', dataIndex: 'version', key: 'version' },
{ title: 'Upgraded', dataIndex: 'upgradeNum', key: 'upgradeNum' },
{ title: 'Creator', dataIndex: 'creator', key: 'creator' },
{ title: 'Date', dataIndex: 'createdAt', key: 'createdAt' },
{ title: 'Action', key: 'operation', render: () => <a>Publish</a> },
];
const data = [];
for (let i = 0; i < 3; ++i) {
data.push({
key: i,
name: 'Screem',
platform: 'iOS',
version: '10.3.4.5654',
upgradeNum: 500,
creator: 'Jack',
createdAt: '2014-12-24 23:12:00',
});
}
return (
<Table
className="components-table-demo-nested"
columns={columns}
expandable={{ expandedRowRender }}
dataSource={data}
/>
);
}
ReactDOM.render(<NestedTable />, mountNode);
使用自定义元素,我们可以集成 react-dnd 来实现拖拽排序。
import React, { useState, useCallback, useRef } from 'react';
import { Table } from 'antd';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import update from 'immutability-helper';
const type = 'DragableBodyRow';
const DragableBodyRow = ({ index, moveRow, className, style, ...restProps }) => {
const ref = useRef();
const [{ isOver, dropClassName }, drop] = useDrop({
accept: type,
collect: monitor => {
const { index: dragIndex } = monitor.getItem() || {};
if (dragIndex === index) {
return {};
}
return {
isOver: monitor.isOver(),
dropClassName: dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',
};
},
drop: item => {
moveRow(item.index, index);
},
});
const [, drag] = useDrag({
type,
item: { index },
collect: monitor => ({
isDragging: monitor.isDragging(),
}),
});
drop(drag(ref));
return (
<tr
ref={ref}
className={`${className}${isOver ? dropClassName : ''}`}
style={{ cursor: 'move', ...style }}
{...restProps}
/>
);
};
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
},
];
const DragSortingTable: React.FC = () => {
const [data, setData] = useState([
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
]);
const components = {
body: {
row: DragableBodyRow,
},
};
const moveRow = useCallback(
(dragIndex, hoverIndex) => {
const dragRow = data[dragIndex];
setData(
update(data, {
$splice: [
[dragIndex, 1],
[hoverIndex, 0, dragRow],
],
}),
);
},
[data],
);
return (
<DndProvider backend={HTML5Backend}>
<Table
columns={columns}
dataSource={data}
components={components}
onRow={(record, index) => ({
index,
moveRow,
})}
/>
</DndProvider>
);
};
ReactDOM.render(<DragSortingTable />, mountNode);
#components-table-demo-drag-sorting tr.drop-over-downward td {
border-bottom: 2px dashed #1890ff;
}
#components-table-demo-drag-sorting tr.drop-over-upward td {
border-top: 2px dashed #1890ff;
}
也可以使用 react-sortable-hoc 来实现一个拖拽操作列。
import { Table } from 'antd';
import { sortableContainer, sortableElement, sortableHandle } from 'react-sortable-hoc';
import { MenuOutlined } from '@ant-design/icons';
import arrayMove from 'array-move';
const DragHandle = sortableHandle(() => <MenuOutlined style={{ cursor: 'grab', color: '#999' }} />);
const columns = [
{
title: 'Sort',
dataIndex: 'sort',
width: 30,
className: 'drag-visible',
render: () => <DragHandle />,
},
{
title: 'Name',
dataIndex: 'name',
className: 'drag-visible',
},
{
title: 'Age',
dataIndex: 'age',
},
{
title: 'Address',
dataIndex: 'address',
},
];
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
index: 0,
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
index: 1,
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
index: 2,
},
];
const SortableItem = sortableElement(props => <tr {...props} />);
const SortableContainer = sortableContainer(props => <tbody {...props} />);
class SortableTable extends React.Component {
state = {
dataSource: data,
};
onSortEnd = ({ oldIndex, newIndex }) => {
const { dataSource } = this.state;
if (oldIndex !== newIndex) {
const newData = arrayMove([].concat(dataSource), oldIndex, newIndex).filter(el => !!el);
console.log('Sorted items: ', newData);
this.setState({ dataSource: newData });
}
};
DraggableContainer = props => (
<SortableContainer
useDragHandle
disableAutoscroll
helperClass="row-dragging"
onSortEnd={this.onSortEnd}
{...props}
/>
);
DraggableBodyRow = ({ className, style, ...restProps }) => {
const { dataSource } = this.state;
// function findIndex base on Table rowKey props and should always be a right array index
const index = dataSource.findIndex(x => x.index === restProps['data-row-key']);
return <SortableItem index={index} {...restProps} />;
};
render() {
const { dataSource } = this.state;
return (
<Table
pagination={false}
dataSource={dataSource}
columns={columns}
rowKey="index"
components={{
body: {
wrapper: this.DraggableContainer,
row: this.DraggableBodyRow,
},
}}
/>
);
}
}
ReactDOM.render(<SortableTable />, mountNode);
.row-dragging {
background: #fafafa;
border: 1px solid #ccc;
}
.row-dragging td {
padding: 16px;
}
.row-dragging .drag-visible {
visibility: visible;
}
设置 column.ellipsis
可以让单元格内容根据宽度自动省略。
列头缩略暂不支持和排序筛选一起使用。
import { Table } from 'antd';
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
render: text => <a>{text}</a>,
width: 150,
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
width: 80,
},
{
title: 'Address',
dataIndex: 'address',
key: 'address 1',
ellipsis: true,
},
{
title: 'Long Column Long Column Long Column',
dataIndex: 'address',
key: 'address 2',
ellipsis: true,
},
{
title: 'Long Column Long Column',
dataIndex: 'address',
key: 'address 3',
ellipsis: true,
},
{
title: 'Long Column',
dataIndex: 'address',
key: 'address 4',
ellipsis: true,
},
];
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park, New York No. 1 Lake Park',
tags: ['nice', 'developer'],
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 2 Lake Park, London No. 2 Lake Park',
tags: ['loser'],
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park, Sidney No. 1 Lake Park',
tags: ['cool', 'teacher'],
},
];
ReactDOM.render(<Table columns={columns} dataSource={data} />, mountNode);
设置 column.ellipsis.showTitle
关闭单元格内容自动省略后默认的 title
提示, 使用 Tooltip
替代。
import { Table, Tooltip } from 'antd';
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
render: text => <a>{text}</a>,
width: 150,
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
width: 80,
},
{
title: 'Address',
dataIndex: 'address',
key: 'address 1',
ellipsis: {
showTitle: false,
},
render: address => (
<Tooltip placement="topLeft" title={address}>
{address}
</Tooltip>
),
},
{
title: 'Long Column Long Column Long Column',
dataIndex: 'address',
key: 'address 2',
ellipsis: {
showTitle: false,
},
render: address => (
<Tooltip placement="topLeft" title={address}>
{address}
</Tooltip>
),
},
{
title: 'Long Column Long Column',
dataIndex: 'address',
key: 'address 3',
ellipsis: {
showTitle: false,
},
render: address => (
<Tooltip placement="topLeft" title={address}>
{address}
</Tooltip>
),
},
{
title: 'Long Column',
dataIndex: 'address',
key: 'address 4',
ellipsis: {
showTitle: false,
},
render: address => (
<Tooltip placement="topLeft" title={address}>
{address}
</Tooltip>
),
},
];
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park, New York No. 1 Lake Park',
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 2 Lake Park, London No. 2 Lake Park',
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park, Sidney No. 1 Lake Park',
},
];
ReactDOM.render(<Table columns={columns} dataSource={data} />, mountNode);
通过 summary
设置总结栏。使用 Table.Summary.Cell
同步 Column 的固定状态。你可以通过配置 Table.Summary
的 fixed
属性使其固定(4.16.0
支持)。
import { Table, Typography } from 'antd';
const { Text } = Typography;
const columns = [
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Borrow',
dataIndex: 'borrow',
},
{
title: 'Repayment',
dataIndex: 'repayment',
},
];
const data = [
{
key: '1',
name: 'John Brown',
borrow: 10,
repayment: 33,
},
{
key: '2',
name: 'Jim Green',
borrow: 100,
repayment: 0,
},
{
key: '3',
name: 'Joe Black',
borrow: 10,
repayment: 10,
},
{
key: '4',
name: 'Jim Red',
borrow: 75,
repayment: 45,
},
];
const fixedColumns = [
{
title: 'Name',
dataIndex: 'name',
fixed: true,
width: 100,
},
{
title: 'Description',
dataIndex: 'description',
},
];
const fixedData = [];
for (let i = 0; i < 20; i += 1) {
fixedData.push({
key: i,
name: ['Light', 'Bamboo', 'Little'][i % 3],
description: 'Everything that has a beginning, has an end.',
});
}
ReactDOM.render(
<>
<Table
columns={columns}
dataSource={data}
pagination={false}
bordered
summary={pageData => {
let totalBorrow = 0;
let totalRepayment = 0;
pageData.forEach(({ borrow, repayment }) => {
totalBorrow += borrow;
totalRepayment += repayment;
});
return (
<>
<Table.Summary.Row>
<Table.Summary.Cell>Total</Table.Summary.Cell>
<Table.Summary.Cell>
<Text type="danger">{totalBorrow}</Text>
</Table.Summary.Cell>
<Table.Summary.Cell>
<Text>{totalRepayment}</Text>
</Table.Summary.Cell>
</Table.Summary.Row>
<Table.Summary.Row>
<Table.Summary.Cell>Balance</Table.Summary.Cell>
<Table.Summary.Cell colSpan={2}>
<Text type="danger">{totalBorrow - totalRepayment}</Text>
</Table.Summary.Cell>
</Table.Summary.Row>
</>
);
}}
/>
<br />
<Table
columns={fixedColumns}
dataSource={fixedData}
pagination={false}
scroll={{ x: 2000, y: 500 }}
bordered
summary={() => (
<Table.Summary fixed>
<Table.Summary.Row>
<Table.Summary.Cell index={0}>Summary</Table.Summary.Cell>
<Table.Summary.Cell index={1}>This is a summary content</Table.Summary.Cell>
</Table.Summary.Row>
</Table.Summary>
)}
/>
</>,
mountNode,
);
通过 react-window
引入虚拟滚动方案,实现 100000 条数据的高性能表格。
TypeScript
JavaScript
import React, { useState, useEffect, useRef } from 'react';
import { VariableSizeGrid as Grid } from 'react-window';
import ResizeObserver from 'rc-resize-observer';
import classNames from 'classnames';
import { Table } from 'antd';
function VirtualTable(props: Parameters<typeof Table>[0]) {
const { columns, scroll } = props;
const [tableWidth, setTableWidth] = useState(0);
const widthColumnCount = columns!.filter(({ width }) => !width).length;
const mergedColumns = columns!.map(column => {
if (column.width) {
return column;
}
return {
...column,
width: Math.floor(tableWidth / widthColumnCount),
};
});
const gridRef = useRef<any>();
const [connectObject] = useState<any>(() => {
const obj = {};
Object.defineProperty(obj, 'scrollLeft', {
get: () => null,
set: (scrollLeft: number) => {
if (gridRef.current) {
gridRef.current.scrollTo({ scrollLeft });
}
},
});
return obj;
});
const resetVirtualGrid = () => {
gridRef.current.resetAfterIndices({
columnIndex: 0,
shouldForceUpdate: true,
});
};
useEffect(() => resetVirtualGrid, [tableWidth]);
const renderVirtualList = (rawData: object[], { scrollbarSize, ref, onScroll }: any) => {
ref.current = connectObject;
const totalHeight = rawData.length * 54;
return (
<Grid
ref={gridRef}
className="virtual-grid"
columnCount={mergedColumns.length}
columnWidth={(index: number) => {
const { width } = mergedColumns[index];
return totalHeight > scroll!.y! && index === mergedColumns.length - 1
? (width as number) - scrollbarSize - 1
: (width as number);
}}
height={scroll!.y as number}
rowCount={rawData.length}
rowHeight={() => 54}
width={tableWidth}
onScroll={({ scrollLeft }: { scrollLeft: number }) => {
onScroll({ scrollLeft });
}}
>
{({
columnIndex,
rowIndex,
style,
}: {
columnIndex: number;
rowIndex: number;
style: React.CSSProperties;
}) => (
<div
className={classNames('virtual-table-cell', {
'virtual-table-cell-last': columnIndex === mergedColumns.length - 1,
})}
style={style}
>
{(rawData[rowIndex] as any)[(mergedColumns as any)[columnIndex].dataIndex]}
</div>
)}
</Grid>
);
};
return (
<ResizeObserver
onResize={({ width }) => {
setTableWidth(width);
}}
>
<Table
{...props}
className="virtual-table"
columns={mergedColumns}
pagination={false}
components={{
body: renderVirtualList,
}}
/>
</ResizeObserver>
);
}
// Usage
const columns = [
{ title: 'A', dataIndex: 'key', width: 150 },
{ title: 'B', dataIndex: 'key' },
{ title: 'C', dataIndex: 'key' },
{ title: 'D', dataIndex: 'key' },
{ title: 'E', dataIndex: 'key', width: 200 },
{ title: 'F', dataIndex: 'key', width: 100 },
];
const data = Array.from({ length: 100000 }, (_, key) => ({ key }));
ReactDOM.render(
<VirtualTable columns={columns} dataSource={data} scroll={{ y: 300, x: '100vw' }} />,
mountNode,
);
响应式配置列的展示。
import { Table } from 'antd';
const columns = [
{
title: 'Name (all screens)',
dataIndex: 'name',
key: 'name',
render: text => <a>{text}</a>,
},
{
title: 'Age (medium screen or bigger)',
dataIndex: 'age',
key: 'age',
responsive: ['md'],
},
{
title: 'Address (large screen or bigger)',
dataIndex: 'address',
key: 'address',
responsive: ['lg'],
},
];
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
},
];
ReactDOM.render(<Table columns={columns} dataSource={data} />, mountNode);
表格的分页设置。
import { Table, Tag, Radio, Space } from 'antd';
const topOptions = [
{ label: 'topLeft', value: 'topLeft' },
{ label: 'topCenter', value: 'topCenter' },
{ label: 'topRight', value: 'topRight' },
{ label: 'none', value: 'none' },
];
const bottomOptions = [
{ label: 'bottomLeft', value: 'bottomLeft' },
{ label: 'bottomCenter', value: 'bottomCenter' },
{ label: 'bottomRight', value: 'bottomRight' },
{ label: 'none', value: 'none' },
];
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
render: text => <a>{text}</a>,
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
},
{
title: 'Tags',
key: 'tags',
dataIndex: 'tags',
render: tags => (
<span>
{tags.map(tag => {
let color = tag.length > 5 ? 'geekblue' : 'green';
if (tag === 'loser') {
color = 'volcano';
}
return (
<Tag color={color} key={tag}>
{tag.toUpperCase()}
</Tag>
);
})}
</span>
),
},
{
title: 'Action',
key: 'action',
render: (text, record) => (
<Space size="middle">
<a>Invite {record.name}</a>
<a>Delete</a>
</Space>
),
},
];
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
tags: ['nice', 'developer'],
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
tags: ['loser'],
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
tags: ['cool', 'teacher'],
},
];
class Demo extends React.Component {
state = {
top: 'topLeft',
bottom: 'bottomRight',
};
render() {
return (
<div>
<div>
<Radio.Group
style={{ marginBottom: 10 }}
options={topOptions}
value={this.state.top}
onChange={e => {
this.setState({ top: e.target.value });
}}
/>
</div>
<Radio.Group
style={{ marginBottom: 10 }}
options={bottomOptions}
value={this.state.bottom}
onChange={e => {
this.setState({ bottom: e.target.value });
}}
/>
<Table
columns={columns}
pagination={{ position: [this.state.top, this.state.bottom] }}
dataSource={data}
/>
</div>
);
}
}
ReactDOM.render(<Demo />, mountNode);
对于长表格,需要滚动才能查看表头和滚动条,那么现在可以设置跟随页面固定表头和滚动条。
import { Table } from 'antd';
const columns = [
{
title: 'Full Name',
width: 100,
dataIndex: 'name',
key: 'name',
fixed: 'left',
},
{
title: 'Age',
width: 100,
dataIndex: 'age',
key: 'age',
fixed: 'left',
},
{
title: 'Column 1',
dataIndex: 'address',
key: '1',
width: 150,
},
{
title: 'Column 2',
dataIndex: 'address',
key: '2',
width: 150,
},
{
title: 'Column 3',
dataIndex: 'address',
key: '3',
width: 150,
},
{
title: 'Column 4',
dataIndex: 'address',
key: '4',
width: 150,
},
{
title: 'Column 5',
dataIndex: 'address',
key: '5',
width: 150,
},
{
title: 'Column 6',
dataIndex: 'address',
key: '6',
width: 150,
},
{
title: 'Column 7',
dataIndex: 'address',
key: '7',
width: 150,
},
{ title: 'Column 8', dataIndex: 'address', key: '8' },
{
title: 'Action',
key: 'operation',
fixed: 'right',
width: 100,
render: () => <a>action</a>,
},
];
const data = [];
for (let i = 0; i < 100; i++) {
data.push({
key: i,
name: `Edrward ${i}`,
age: 32,
address: `London Park no. ${i}`,
});
}
ReactDOM.render(
<Table
columns={columns}
dataSource={data}
scroll={{ x: 1500 }}
summary={pageData => (
<Table.Summary fixed>
<Table.Summary.Row>
<Table.Summary.Cell index={0} colSpan={2}>
Fix Left
</Table.Summary.Cell>
<Table.Summary.Cell index={2} colSpan={8}>
Scroll Context
</Table.Summary.Cell>
<Table.Summary.Cell index={10}>Fix Right</Table.Summary.Cell>
</Table.Summary.Row>
</Table.Summary>
)}
sticky
/>,
mountNode,
);
选择不同配置组合查看效果。
import { Table, Switch, Radio, Form, Space } from 'antd';
import { DownOutlined } from '@ant-design/icons';
const columns = [
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Age',
dataIndex: 'age',
sorter: (a, b) => a.age - b.age,
},
{
title: 'Address',
dataIndex: 'address',
filters: [
{
text: 'London',
value: 'London',
},
{
text: 'New York',
value: 'New York',
},
],
onFilter: (value, record) => record.address.indexOf(value) === 0,
},
{
title: 'Action',
key: 'action',
sorter: true,
render: () => (
<Space size="middle">
<a>Delete</a>
<a className="ant-dropdown-link">
More actions <DownOutlined />
</a>
</Space>
),
},
];
const data = [];
for (let i = 1; i <= 10; i++) {
data.push({
key: i,
name: 'John Brown',
age: `${i}2`,
address: `New York No. ${i} Lake Park`,
description: `My name is John Brown, I am ${i}2 years old, living in New York No. ${i} Lake Park.`,
});
}
const expandable = { expandedRowRender: record => <p>{record.description}</p> };
const title = () => 'Here is title';
const showHeader = true;
const footer = () => 'Here is footer';
const pagination = { position: 'bottom' };
class Demo extends React.Component {
state = {
bordered: false,
loading: false,
pagination,
size: 'default',
expandable,
title: undefined,
showHeader,
footer,
rowSelection: {},
scroll: undefined,
hasData: true,
tableLayout: undefined,
top: 'none',
bottom: 'bottomRight',
};
handleToggle = prop => enable => {
this.setState({ [prop]: enable });
};
handleSizeChange = e => {
this.setState({ size: e.target.value });
};
handleTableLayoutChange = e => {
this.setState({ tableLayout: e.target.value });
};
handleExpandChange = enable => {
this.setState({ expandable: enable ? expandable : undefined });
};
handleEllipsisChange = enable => {
this.setState({ ellipsis: enable });
};
handleTitleChange = enable => {
this.setState({ title: enable ? title : undefined });
};
handleHeaderChange = enable => {
this.setState({ showHeader: enable ? showHeader : false });
};
handleFooterChange = enable => {
this.setState({ footer: enable ? footer : undefined });
};
handleRowSelectionChange = enable => {
this.setState({ rowSelection: enable ? {} : undefined });
};
handleYScrollChange = enable => {
this.setState({ yScroll: enable });
};
handleXScrollChange = e => {
this.setState({ xScroll: e.target.value });
};
handleDataChange = hasData => {
this.setState({ hasData });
};
render() {
const { xScroll, yScroll, ...state } = this.state;
const scroll = {};
if (yScroll) {
scroll.y = 240;
}
if (xScroll) {
scroll.x = '100vw';
}
const tableColumns = columns.map(item => ({ ...item, ellipsis: state.ellipsis }));
if (xScroll === 'fixed') {
tableColumns[0].fixed = true;
tableColumns[tableColumns.length - 1].fixed = 'right';
}
return (
<>
<Form
layout="inline"
className="components-table-demo-control-bar"
style={{ marginBottom: 16 }}
>
<Form.Item label="Bordered">
<Switch checked={state.bordered} onChange={this.handleToggle('bordered')} />
</Form.Item>
<Form.Item label="loading">
<Switch checked={state.loading} onChange={this.handleToggle('loading')} />
</Form.Item>
<Form.Item label="Title">
<Switch checked={!!state.title} onChange={this.handleTitleChange} />
</Form.Item>
<Form.Item label="Column Header">
<Switch checked={!!state.showHeader} onChange={this.handleHeaderChange} />
</Form.Item>
<Form.Item label="Footer">
<Switch checked={!!state.footer} onChange={this.handleFooterChange} />
</Form.Item>
<Form.Item label="Expandable">
<Switch checked={!!state.expandable} onChange={this.handleExpandChange} />
</Form.Item>
<Form.Item label="Checkbox">
<Switch checked={!!state.rowSelection} onChange={this.handleRowSelectionChange} />
</Form.Item>
<Form.Item label="Fixed Header">
<Switch checked={!!yScroll} onChange={this.handleYScrollChange} />
</Form.Item>
<Form.Item label="Has Data">
<Switch checked={!!state.hasData} onChange={this.handleDataChange} />
</Form.Item>
<Form.Item label="Ellipsis">
<Switch checked={!!state.ellipsis} onChange={this.handleEllipsisChange} />
</Form.Item>
<Form.Item label="Size">
<Radio.Group value={state.size} onChange={this.handleSizeChange}>
<Radio.Button value="default">Default</Radio.Button>
<Radio.Button value="middle">Middle</Radio.Button>
<Radio.Button value="small">Small</Radio.Button>
</Radio.Group>
</Form.Item>
<Form.Item label="Table Scroll">
<Radio.Group value={xScroll} onChange={this.handleXScrollChange}>
<Radio.Button value={undefined}>Unset</Radio.Button>
<Radio.Button value="scroll">Scroll</Radio.Button>
<Radio.Button value="fixed">Fixed Columns</Radio.Button>
</Radio.Group>
</Form.Item>
<Form.Item label="Table Layout">
<Radio.Group value={state.tableLayout} onChange={this.handleTableLayoutChange}>
<Radio.Button value={undefined}>Unset</Radio.Button>
<Radio.Button value="fixed">Fixed</Radio.Button>
</Radio.Group>
</Form.Item>
<Form.Item label="Pagination Top">
<Radio.Group
value={this.state.top}
onChange={e => {
this.setState({ top: e.target.value });
}}
>
<Radio.Button value="topLeft">TopLeft</Radio.Button>
<Radio.Button value="topCenter">TopCenter</Radio.Button>
<Radio.Button value="topRight">TopRight</Radio.Button>
<Radio.Button value="none">None</Radio.Button>
</Radio.Group>
</Form.Item>
<Form.Item label="Pagination Bottom">
<Radio.Group
value={this.state.bottom}
onChange={e => {
this.setState({ bottom: e.target.value });
}}
>
<Radio.Button value="bottomLeft">BottomLeft</Radio.Button>
<Radio.Button value="bottomCenter">BottomCenter</Radio.Button>
<Radio.Button value="bottomRight">BottomRight</Radio.Button>
<Radio.Button value="none">None</Radio.Button>
</Radio.Group>
</Form.Item>
</Form>
<Table
{...this.state}
pagination={{ position: [this.state.top, this.state.bottom] }}
columns={tableColumns}
dataSource={state.hasData ? data : null}
scroll={scroll}
/>
</>
);
}
}
ReactDOM.render(<Demo />, mountNode);
API
另外我们封装了 ProTable,在 antd
Table 之上扩展了更多便捷易用的功能,内置搜索、筛选、刷新等常用表格行为,并为多种类型数据展示提供了内置格式化,欢迎尝试使用。
Table
参数 | 说明 | 类型 | 默认值 | 版本 |
---|---|---|---|---|
bordered | 是否展示外边框和列边框 | boolean | false | |
columns | 表格列的配置描述,具体项见下表 | ColumnsType[] | - | |
components | 覆盖默认的 table 元素 | TableComponents | - | |
dataSource | 数据数组 | object[] | - | |
expandable | 配置展开属性 | expandable | - | |
footer | 表格尾部 | function(currentPageData) | - | |
getPopupContainer | 设置表格内各类浮层的渲染节点,如筛选菜单 | (triggerNode) => HTMLElement | () => TableHtmlElement | |
loading | 页面是否加载中 | boolean | Spin Props | false | |
locale | 默认文案设置,目前包括排序、过滤、空数据文案 | object | filterConfirm: 确定 filterReset: 重置 emptyText: 暂无数据 默认值 | |
pagination | 分页器,参考配置项或 pagination 文档,设为 false 时不展示和进行分页 | object | - | |
rowClassName | 表格行的类名 | function(record, index): string | - | |
rowKey | 表格行 key 的取值,可以是字符串或一个函数 | string | function(record): string | key | |
rowSelection | 表格行是否可选择,配置项 | object | - | |
scroll | 表格是否可滚动,也可以指定滚动区域的宽、高,配置项 | object | - | |
showHeader | 是否显示表头 | boolean | true | |
showSorterTooltip | 表头是否显示下一次排序的 tooltip 提示。当参数类型为对象时,将被设置为 Tooltip 的属性 | boolean | Tooltip props | true | |
size | 表格大小 | default | middle | small | default | |
sortDirections | 支持的排序方式,取值为 ascend descend | Array | [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
。
<Table
onRow={record => {
return {
onClick: event => {}, // 点击行
onDoubleClick: event => {},
onContextMenu: event => {},
onMouseEnter: event => {}, // 鼠标移入行
onMouseLeave: event => {},
};
}}
onHeaderRow={(columns, index) => {
return {
onClick: () => {}, // 点击表头行
};
}}
/>
Column
列描述数据对象,是 columns 中的一项,Column 使用相同的 API。
参数 | 说明 | 类型 | 默认值 | 版本 |
---|---|---|---|---|
align | 设置列的对齐方式 | left | right | center | left | |
className | 列样式类名 | string | - | |
colSpan | 表头列合并,设置为 0 时,不渲染 | number | - | |
dataIndex | 列数据在数据项中对应的路径,支持通过数组查询嵌套路径 | string | string[] | - | |
defaultFilteredValue | 默认筛选值 | string[] | - | |
defaultSortOrder | 默认排序顺序 | ascend | descend | - | |
editable | 是否可编辑 | boolean | false | |
ellipsis | 超过宽度将自动省略,暂不支持和排序筛选一起使用。 设置为 true 或 { showTitle?: boolean } 时,表格布局将变成 tableLayout=”fixed” 。 | boolean | { showTitle?: boolean } | false | showTitle: 4.3.0 |
filterDropdown | 可以自定义筛选菜单,此函数只负责渲染图层,需要自行编写各种交互 | ReactNode | (props: FilterDropdownProps) => ReactNode | - | |
filterDropdownVisible | 用于控制自定义筛选菜单是否可见 | boolean | - | |
filtered | 标识数据是否经过过滤,筛选图标会高亮 | boolean | false | |
filteredValue | 筛选的受控属性,外界可用此控制列的筛选状态,值为已筛选的 value 数组 | string[] | - | |
filterIcon | 自定义 filter 图标。 | ReactNode | (filtered: boolean) => ReactNode | false | |
filterMultiple | 是否多选 | boolean | true | |
filters | 表头的筛选菜单项 | object[] | - | |
fixed | (IE 下无效)列是否固定,可选 true (等效于 left) left right | boolean | string | false | |
key | React 需要的 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 中 showSorterTooltip | boolean | Tooltip props | true | |
sortDirections | 支持的排序方式,覆盖 Table 中 sortDirections , 取值为 ascend descend | Array | [ascend , descend ] | |
sorter | 排序函数,本地排序使用一个函数(参考 Array.sort 的 compareFunction),需要服务端排序可设为 true | function | boolean | - | |
sortOrder | 排序的受控属性,外界可用此控制列的排序,可设置为 ascend descend false | boolean | 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 | bottomRight | Array | [bottomRight ] |
更多配置项,请查看 Pagination
。
expandable
展开功能的配置。
参数 | 说明 | 类型 | 默认值 | 版本 |
---|---|---|---|---|
childrenColumnName | 指定树形结构的列名 | string | children | |
columnWidth | 自定义展开列宽度 | string | number | - | |
defaultExpandAllRows | 初始时,是否展开所有行 | boolean | false | |
defaultExpandedRowKeys | 默认展开的行 | string[] | - | |
expandedRowClassName | 展开行的 className | function(record, index, indent): string | - | |
expandedRowKeys | 展开的行,控制属性 | string[] | - | |
expandedRowRender | 额外的展开行 | function(record, index, indent, expanded): ReactNode | - | |
expandIcon | 自定义展开图标,参考示例 | function(props): ReactNode | - | |
expandIconColumnIndex | 自定义展开按钮的列顺序,-1 时不展示 | number | - | |
expandRowByClick | 通过点击行来展开子行 | boolean | false | |
fixed | 控制展开图标是否固定,可选 true left right | boolean | string | false | 4.16.0 |
indentSize | 展示树形数据时,每层缩进的宽度,以 px 为单位 | number | 15 | |
rowExpandable | 设置是否允许行展开 | (record) => boolean | - | |
onExpand | 点击展开图标时触发 | function(expanded, record) | - | |
onExpandedRowsChange | 展开的行变化时触发 | function(expandedRows) | - |
fixed
当设置为 true 或
left
且expandIconColumnIndex
未设置或为 0 时,开启固定当设置为 true 或
right
且expandIconColumnIndex
设置为表格列数时,开启固定
rowSelection
选择功能的配置。
参数 | 说明 | 类型 | 默认值 | 版本 |
---|---|---|---|---|
checkStrictly | checkable 状态下节点选择完全受控(父子数据选中状态不再关联) | boolean | true | 4.4.0 |
columnTitle | 自定义列表选择框标题 | ReactNode | - | |
columnWidth | 自定义列表选择框宽度 | string | number | 32px | |
fixed | 把选择框列固定在左边 | boolean | - | |
getCheckboxProps | 选择框的默认属性配置 | function(record) | - | |
hideSelectAll | 隐藏全选勾选框与自定义选择项 | boolean | false | 4.3.0 |
preserveSelectedRowKeys | 当数据被删除时仍然保留选项的 key | boolean | - | 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[] | boolean | true | |
type | 多选/单选 | checkbox | radio | checkbox | |
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
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
key | React 需要的 key,建议设置 | string | - |
text | 选择项显示的文字 | ReactNode | - |
onSelect | 选择项点击回调 | function(changeableRowKeys) | - |
在 TypeScript 中使用
import { Table } from 'antd';
import { ColumnsType } from 'antd/es/table';
interface User {
key: number;
name: string;
}
const columns: ColumnsType<User> = [
{
key: 'name',
title: 'Name',
dataIndex: 'name',
},
];
const data: User[] = [
{
key: 0,
name: 'Jack',
},
];
export default () => (
<>
<Table<User> columns={columns} dataSource={data} />
/* 使用 JSX 风格的 API */
<Table<User> dataSource={data}>
<Table.Column<User> key="name" title="Name" dataIndex="name" />
</Table>
</>
);
TypeScript 里使用 Table 的 CodeSandbox 实例。
注意
按照 React 的规范,所有的数组组件必须绑定 key
。在 Table 中,dataSource
和 columns
里的数据值都需要指定 key
值。对于 dataSource
默认将每列数据的 key
属性作为唯一的标识。
如果 dataSource[i].key
没有提供,你应该使用 rowKey
来指定 dataSource
的主键,如下所示。若没有指定,控制台会出现以上的提示,表格组件也会出现各类奇怪的错误。
// 比如你的数据主键是 uid
return <Table rowKey="uid" />;
// 或
return <Table rowKey={record => record.uid} />;
从 v3 升级到 v4
Table 移除了在 v3 中废弃的 onRowClick
、onRowDoubleClick
、onRowMouseEnter
、onRowMouseLeave
等方法。如果你使用的 api 为文档中列举的 api,那你不用担心会丢失功能。
此外,比较重大的改动为 dataIndex
从支持路径嵌套如 user.age
改成了数组路径如 ['user', 'age']
。以解决过去属性名带 .
需要额外的数据转化问题。
FAQ
如何在没有数据或只有一页数据时隐藏分页栏
你可以设置 pagination
的 hideOnSinglePage
属性为 true
。
表格过滤时会回到第一页?
前端过滤时通常条目总数会减少,从而导致总页数小于筛选前的当前页数,为了防止当前页面没有数据,我们默认会返回第一页。
如果你在使用远程分页,很可能需要保持当前页面,你可以参照这个 受控例子 控制当前页面不变。
表格分页为何会出现 size 切换器?
自 4.1.0
起,Pagination 在 total
大于 50 条时会默认显示 size 切换器以提升用户交互体验。如果你不需要该功能,可以通过设置 showSizeChanger
为 false
来关闭。
为什么 更新 state 会导致全表渲染?
由于 columns
支持 render
方法,因而 Table 无法知道哪些单元会受到影响。你可以通过 column.shouldCellUpdate
来控制单元格的渲染。
固定列穿透到最上层该怎么办?
固定列通过 z-index
属性将其悬浮于非固定列之上,这使得有时候你会发现在 Table 上放置遮罩层时固定列会被透过的情况。为遮罩层设置更高的 z-index
覆盖住固定列即可。