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" /><Columntitle="Tags"dataIndex="tags"key="tags"render={tags => (<>{tags.map(tag => (<Tag color="blue" key={tag}>{tag}</Tag>))}</>)}/><Columntitle="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 selectionconst 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 checkedname: record.name,}),};const Demo = () => {const [selectionType, setSelectionType] = useState<'checkbox' | 'radio'>('checkbox');return (<div><Radio.GrouponChange={({ target: { value } }) => {setSelectionType(value);}}value={selectionType}><Radio value="checkbox">Checkbox</Radio><Radio value="radio">radio</Radio></Radio.Group><Divider /><TablerowSelection={{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 columnloading: false,};start = () => {this.setState({ loading: true });// ajax request after empty completingsetTimeout(() => {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 }}><Inputref={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><Buttontype="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><Buttontype="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 ? (<HighlighterhighlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}searchWords={[this.state.searchText]}autoEscapetextToHighlight={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 (<Tablecolumns={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(<Tablecolumns={columns}dataSource={data}borderedtitle={() => '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(<Tablecolumns={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 0const 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 cellif (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 selectionconst 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><Tablecolumns={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(<Tablecolumns={columns}dataSource={data}borderedsize="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.Itemstyle={{ 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><Tablecomponents={components}rowClassName={() => 'editable-row'}bordereddataSource={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.Itemname={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}><Tablecomponents={{body: {cell: EditableCell,},}}bordereddataSource={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 (<TableclassName="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 (<trref={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}><Tablecolumns={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 => (<SortableContaineruseDragHandledisableAutoscrollhelperClass="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 indexconst index = dataSource.findIndex(x => x.index === restProps['data-row-key']);return <SortableItem index={index} {...restProps} />;};render() {const { dataSource } = this.state;return (<Tablepagination={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(<><Tablecolumns={columns}dataSource={data}pagination={false}borderedsummary={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 /><Tablecolumns={fixedColumns}dataSource={fixedData}pagination={false}scroll={{ x: 2000, y: 500 }}borderedsummary={() => (<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 (<Gridref={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;}) => (<divclassName={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 (<ResizeObserveronResize={({ width }) => {setTableWidth(width);}}><Table{...props}className="virtual-table"columns={mergedColumns}pagination={false}components={{body: renderVirtualList,}}/></ResizeObserver>);}// Usageconst 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.Groupstyle={{ marginBottom: 10 }}options={topOptions}value={this.state.top}onChange={e => {this.setState({ top: e.target.value });}}/></div><Radio.Groupstyle={{ marginBottom: 10 }}options={bottomOptions}value={this.state.bottom}onChange={e => {this.setState({ bottom: e.target.value });}}/><Tablecolumns={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(<Tablecolumns={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 (<><Formlayout="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.Groupvalue={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.Groupvalue={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。
<TableonRow={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 的主键,如下所示。若没有指定,控制台会出现以上的提示,表格组件也会出现各类奇怪的错误。
// 比如你的数据主键是 uidreturn <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 覆盖住固定列即可。