Table 表格

展示行列数据。

何时使用#

  • 当有大量结构化的数据需要展现时;
  • 当需要对数据进行排序、搜索、分页、自定义操作等复杂行为时。

如何使用#

Table 有两种模式,本地数据和远程数据模式。

本地数据模式是指数据一次性载入内存,纯前端进行分页、筛选、排序等功能。

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

var dataSource = [{
  key: '1',
  name: '胡彦斌',
  age: 32,
  address: '西湖区湖底公园1号'
}, {
  key: '2',
  name: '胡彦祖',
  age: 42,
  address: '西湖区湖底公园1号'
}];

<Table dataSource={dataSource} />

远程数据模式是更常见的业务场景,是一次只从服务端读取一页的数据放在前端,执行筛选、排序、切换页码等操作时均向后台发送请求,后台返回当页的数据和相关分页信息。

远程数据模式后续可能删除,目前不推荐使用。

通过指定表格的数据源 dataSource 为一个 DataSource 的实例如下。

var dataSource = new Table.DataSource({
  url: '/api/users',
  resolve: function(result) {
    return result.data;
  },
  getPagination: function(result) {},
  getParams: function(pagination, filters, sorter) {}
});

<Table dataSource={dataSource} />

API#

Table#

参数 说明 类型 可选值 默认值
rowSelection 列表项是否可选择 Object false
pagination 分页器 Object 配置项参考 pagination,设为 false 时不显示分页
size 正常或迷你类型 String default or small default
dataSource 数据源,可以为数组(本地模式)或一个数据源描述对象(远程模式) Array or Object
columns 表格列的配置描述,具体项见下表 Array
rowKey 表格列 key 的取值 Function(recode, index):string record.key
expandIconAsCell 设置展开 Icon 是否单独一列 Boolean true
onChange 分页、排序、筛选变化时触发 Function(pagination, filters, sorter)
loading 页面是否加载中 Boolean false

Column#

列描述数据对象,是 columns 中的一项。

参数 说明 类型 可选值 默认值
title 列头显示文字 String or React.Element
dataIndex 列数据在 data 中对应的 key String
colSpan 表头列合并,设置为 0 时,不渲染 Number
key React 需要的 key String
render 生成复杂数据的渲染函数,参数分别为当前列的值,当前列数据,列索引,@return里面可以设置表格行/列合并 Function(text, record, index) {}
filters 表头的筛选菜单项 Array
onFilter 本地模式下,确定筛选的运行函数 Function
filterMultiple 是否多选 Boolean true
sorter 排序函数,本地模式下为一个函数,远程模式下为布尔值 Function or Boolean
width 列宽度 String or Number
className 列的 className String

dataSource#

远程数据源配置对象。

参数 说明 类型 可选值 默认值
url 数据源地址 String
resolve 获得数据的解析函数,接收参数为远程数据返回的 result Function
getPagination 和后台接口返回的分页数据进行适配的函数,返回值会传给表格中的分页器 Function
getParams 和后台接口接收的参数进行适配,返回值会作为请求的参数发送 Function

注意#

按照 React 的规范,所有的组件数组必须绑定 key。在 Table 中,默认将每列数据的 key 属性作为唯一的标识。

如果你的数据没有这个属性,务必使用 rowKey 来指定数据列的主键。若没有指定,控制台会出现以下的提示,表格组件也会出现各类奇怪的错误。

const rowKey = function(record) {
  return record.uid;  // 比如你的数据主键是 uid
};

return <Table rowKey={rowKey} />;

代码演示

import { Table, Icon } from 'antd';

const columns = [{
  title: '姓名',
  dataIndex: 'name',
  render: function(text) {
    return <a href="#">{text}</a>;
  }
}, {
  title: '年龄',
  dataIndex: 'age'
}, {
  title: '住址',
  dataIndex: 'address'
}, {
  title: '操作',
  dataIndex: '',
  render: function(text, record) {
    return <span>
      <a href="#">操作一</a>
      <span className="ant-divider"></span>
      <a href="#">操作二</a>
      <span className="ant-divider"></span>
      <a href="#" className="ant-dropdown-link">
        更多 <Icon type="down" />
      </a>
    </span>;
  }
}];
const data = [{
  key: '1',
  name: '胡彦斌',
  age: 32,
  address: '西湖区湖底公园1号'
}, {
  key: '2',
  name: '胡彦祖',
  age: 42,
  address: '西湖区湖底公园1号'
}, {
  key: '3',
  name: '李大嘴',
  age: 32,
  address: '西湖区湖底公园1号'
}];

ReactDOM.render(<Table columns={columns} dataSource={data} />
, document.getElementById('components-table-demo-basic'));

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

import { Table } from 'antd';

const columns = [{
  title: '姓名',
  dataIndex: 'name',
  render: function(text) {
    return <a href="#">{text}</a>;
  }
}, {
  title: '年龄',
  dataIndex: 'age'
}, {
  title: '住址',
  dataIndex: 'address'
}];
const data = [{
  key: '1',
  name: '胡彦斌',
  age: 32,
  address: '西湖区湖底公园1号'
}, {
  key: '2',
  name: '胡彦祖',
  age: 42,
  address: '西湖区湖底公园1号'
}, {
  key: '3',
  name: '李大嘴',
  age: 32,
  address: '西湖区湖底公园1号'
}];

// 通过 rowSelection 对象表明需要行选择
const rowSelection = {
  onSelect: function(record, selected, selectedRows) {
    console.log(record, selected, selectedRows);
  },
  onSelectAll: function(selected, selectedRows) {
    console.log(selected, selectedRows);
  }
};

ReactDOM.render(<Table rowSelection={rowSelection} columns={columns} dataSource={data} />
, document.getElementById('components-table-demo-row-selection'));

第一列是联动的选择框,rowSelection 中配置 type="radio" 可设为单选。

import { Table } from 'antd';

const columns = [{
  title: '姓名',
  dataIndex: 'name',
  render: function(text) {
    return <a href="#">{text}</a>;
  }
}, {
  title: '年龄',
  dataIndex: 'age'
}, {
  title: '住址',
  dataIndex: 'address'
}];
const data = [{
  key: '1',
  name: '胡彦斌',
  age: 32,
  address: '西湖区湖底公园1号'
}, {
  key: '2',
  name: '胡彦祖',
  age: 42,
  address: '西湖区湖底公园1号'
}, {
  key: '3',
  name: '李大嘴',
  age: 32,
  address: '西湖区湖底公园1号'
}];

// 通过 rowSelection 对象表明需要行选择
const rowSelection = {
  getCheckboxProps: function(record) {
    return {
      defaultChecked: record.name === '李大嘴', // 配置默认勾选的列
      disabled: record.name === '胡彦祖'    // 配置无法勾选的列
    };
  },
  onSelect: function(record, selected, selectedRows) {
    console.log(record, selected, selectedRows);
  },
  onSelectAll: function(selected, selectedRows) {
    console.log(selected, selectedRows);
  }
};

ReactDOM.render(<Table rowSelection={rowSelection} columns={columns} dataSource={data} />
, document.getElementById('components-table-demo-row-selection-props'));

配置选择框的默认属性。

import { Table } from 'antd';

const columns = [{
  title: '姓名',
  dataIndex: 'name',
  render: function(text) {
    return <a href="#">{text}</a>;
  }
}, {
  title: '年龄',
  dataIndex: 'age'
}, {
  title: '住址',
  dataIndex: 'address'
}];
const data = [{
  key: '1',
  name: '胡彦斌',
  age: 32,
  address: '西湖区湖底公园1号'
}, {
  key: '2',
  name: '胡彦祖',
  age: 42,
  address: '西湖区湖底公园1号'
}, {
  key: '3',
  name: '李大嘴',
  age: 32,
  address: '西湖区湖底公园1号'
}];

// 通过 rowSelection 对象表明需要行选择
const rowSelection = {
  type: 'radio',
  getCheckboxProps: function(record) {
    return {
      defaultChecked: record.name === '李大嘴', // 配置默认勾选的列
      disabled: record.name === '胡彦祖'    // 配置无法勾选的列
    };
  },
  onSelect: function(record, selected, selectedRows) {
    console.log(record, selected, selectedRows);
  },
  onSelectAll: function(selected, selectedRows) {
    console.log(selected, selectedRows);
  }
};

ReactDOM.render(<Table rowSelection={rowSelection} columns={columns} dataSource={data} />
, document.getElementById('components-table-demo-row-selection-radio-props'));

配置单选框的默认属性。

import { Table } from 'antd';

const columns = [{
  title: '姓名',
  dataIndex: 'name',
  render: function(text) {
    return <a href="#">{text}</a>;
  }
}, {
  title: '年龄',
  dataIndex: 'age'
}, {
  title: '住址',
  dataIndex: 'address'
}];

const data = [];
for (let i = 0; i < 46; i++) {
  data.push({
    key: i,
    name: '李大嘴' + i,
    age: 32,
    address: '西湖区湖底公园' + i + '号'
  });
}

const pagination = {
  total: data.length,
  current: 2,
  showSizeChanger: true
};

ReactDOM.render(<Table columns={columns} dataSource={data} pagination={pagination} />
, document.getElementById('components-table-demo-paging'));

数据项较多时显示分页。

import { Table } from 'antd';

const columns = [{
  title: '姓名',
  dataIndex: 'name',
  filters: [{
    text: '姓李的',
    value: '李'
  }, {
    text: '姓胡的',
    value: '胡'
  }],
  // 指定确定筛选的条件函数
  // 这里是名字中第一个字是 value
  onFilter: function(value, record) {
    return record.name.indexOf(value) === 0;
  },
  sorter: function(a, b) {
    return a.name.length - b.name.length;
  }
}, {
  title: '年龄',
  dataIndex: 'age',
  sorter: function(a, b) {
    return a.age - b.age;
  }
}, {
  title: '地址',
  dataIndex: 'address',
  filters: [{
    text: '南湖',
    value: '南湖'
  }, {
    text: '西湖',
    value: '西湖'
  }],
  filterMultiple: false,
  onFilter: function(value, record) {
    return record.address.indexOf(value) === 0;
  },
  sorter: function(a, b) {
    return a.address.length - b.address.length;
  }
}];

const data = [{
  key: '1',
  name: '胡斌',
  age: 32,
  address: '南湖区湖底公园1号'
}, {
  key: '2',
  name: '胡彦祖',
  age: 42,
  address: '西湖区湖底公园12号'
}, {
  key: '3',
  name: '李大嘴',
  age: 32,
  address: '南湖区湖底公园123号'
}, {
  key: '4',
  name: '李秀莲大嘴哥',
  age: 32,
  address: '西湖区湖底公园123号'
}];

function onChange(pagination, filters, sorter) {
  // 点击分页、筛选、排序时触发
  console.log('各类参数是', pagination, filters, sorter);
}

ReactDOM.render(<Table columns={columns} dataSource={data} onChange={onChange} />
, document.getElementById('components-table-demo-head'));

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

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

import { Table, Button } from 'antd';

const columns = [{
  title: '姓名',
  dataIndex: 'name',
  filters: [{
    text: '姓李的',
    value: '李'
  }, {
    text: '姓胡的',
    value: '胡'
  }]
}, {
  title: '年龄',
  dataIndex: 'age',
  sorter: true
}, {
  title: '住址',
  dataIndex: 'address'
}];

const dataSource = new Table.DataSource({
  url: '/components/table/demo/data.json',
  resolve: function(result) {
    return result.data;
  },
  data: {},
  // 和后台接口返回的分页数据进行适配
  getPagination: function(result) {
    return {
      total: result.totalCount,
      pageSize: result.pageSize
    };
  },
  // 和后台接口接收的参数进行适配
  // 参数里提供了分页、筛选、排序的信息
  getParams: function(pagination, filters, sorter) {
    console.log('getParams 的参数是:', pagination, filters, sorter);
    const params = {
      pageSize: pagination.pageSize,
      currentPage: pagination.current,
      sortField: sorter.field,
      sortOrder: sorter.order
    };
    for (let key in filters) {
      params[key] = filters[key];
    }
    console.log('请求参数:', params);
    return params;
  }
});

const Test = React.createClass({
  getInitialState() {
    return {
      dataSource: null,
      pagination: {
        onChange: this.hanlePageChange
      }
    };
  },
  hanlePageChange(page) {
    // 使用受控属性 current,方便外部设置页数
    const pagination = this.state.pagination;
    pagination.current = page;
    this.setState({ pagination });
  },
  refresh() {
    // 回到第一页
    const pagination = this.state.pagination;
    pagination.current = 1;
    this.setState({
      dataSource: dataSource.clone()
    });
  },
  changeAndRefresh() {
    // 回到第一页
    const pagination = this.state.pagination;
    pagination.current = 1;
    // 可以修改原来的 dataSource 再发请求
    this.setState({
      dataSource: dataSource.clone({
        data: { city: 'hz' }
      }),
      pagination,
    });
  },
  render() {
    return <div>
      <Table columns={columns} dataSource={this.state.dataSource} pagination={this.state.pagination} />
      <Button type="primary" onClick={this.refresh}>
        加载初始数据
      </Button>
      &nbsp;
      <Button onClick={this.changeAndRefresh}>
        加载 city=hz 的数据
      </Button>
    </div>;
  }
});

ReactDOM.render(<Test />, document.getElementById('components-table-demo-ajax'));

远程读取的表格是更为常见的模式,下面的表格使用了 dataSource 对象和远程数据源绑定和适配,并具有筛选、排序等功能以及页面 loading 效果。

注意,此示例是静态数据模拟,数据并不准确,请打开网络面板查看请求。

import { Table } from 'antd';

const columns = [{
  title: '姓名',
  dataIndex: 'name'
}, {
  title: '年龄',
  dataIndex: 'age'
}, {
  title: '住址',
  dataIndex: 'address'
}];

const data = [{
  key: '1',
  name: '胡彦斌',
  age: 32,
  address: '西湖区湖底公园1号'
}, {
  key: '2',
  name: '胡彦祖',
  age: 42,
  address: '西湖区湖底公园1号'
}, {
  key: '3',
  name: '李大嘴',
  age: 32,
  address: '西湖区湖底公园1号'
}];

ReactDOM.render(<Table columns={columns} dataSource={data} pagination={false} />
, document.getElementById('components-table-demo-nopagination'));

传入 pagination 为 false 即可。

import { Table } from 'antd';

const columns = [{
  title: '姓名',
  dataIndex: 'name'
}, {
  title: '年龄',
  dataIndex: 'age'
}, {
  title: '住址',
  dataIndex: 'address'
}];
const data = [{
  key: '1',
  name: '胡彦斌',
  age: 32,
  address: '西湖区湖底公园1号'
}, {
  key: '2',
  name: '胡彦祖',
  age: 42,
  address: '西湖区湖底公园1号'
}, {
  key: '3',
  name: '李大嘴',
  age: 32,
  address: '西湖区湖底公园1号'
}];

ReactDOM.render(<Table columns={columns} dataSource={data} size="small" />
, document.getElementById('components-table-demo-small'));

size="small", 用在对话框等空间较小的地方。

import { Table } from 'antd';

const columns = [{
  title: '姓名',
  dataIndex: 'name',
  render: function(text) {
    return <a href="#">{text}</a>;
  }
}, {
  title: '资产',
  className: 'column-money',
  dataIndex: 'money'
}, {
  title: '住址',
  dataIndex: 'address'
}];

const data = [{
  key: '1',
  name: '胡彦斌',
  money: '¥300,000.00',
  address: '西湖区湖底公园1号'
}, {
  key: '2',
  name: '胡彦祖',
  money: '¥1,256,000.00',
  address: '西湖区湖底公园1号'
}, {
  key: '3',
  name: '李大嘴',
  money: '¥120,000.00',
  address: '西湖区湖底公园1号'
}];

ReactDOM.render(<Table columns={columns} dataSource={data} bordered />
, document.getElementById('components-table-demo-bordered'));
.column-money {
  text-align: right;
}

添加表格边框线,bordered

import { Table, Button } from 'antd';

const columns = [{
  title: '姓名',
  dataIndex: 'name',
  render: function(text) {
    return <a href="#">{text}</a>;
  }
}, {
  title: '年龄',
  dataIndex: 'age'
}, {
  title: '住址',
  dataIndex: 'address'
}];
const data1 = [{
  key: '1',
  name: '胡彦斌',
  age: 32,
  address: '西湖区湖底公园1号'
}, {
  key: '2',
  name: '胡彦祖',
  age: 42,
  address: '西湖区湖底公园1号'
}, {
  key: '3',
  name: '李大嘴',
  age: 32,
  address: '西湖区湖底公园1号'
}];
const data2 = [{
  key: '11',
  name: '胡彦斌2',
  age: 32,
  address: '西湖区湖底公园2号'
}, {
  key: '22',
  name: '胡彦祖2',
  age: 42,
  address: '西湖区湖底公园2号'
}, {
  key: '33',
  name: '李大嘴2',
  age: 32,
  address: '西湖区湖底公园2号'
}];

const App = React.createClass({
  getInitialState() {
    return {
      data: []
    };
  },
  handleClick1() {
    this.setState({
      data: data1
    });
  },
  handleClick2() {
    this.setState({
      data: data2
    });
  },
  render() {
    return <div>
      <Table columns={columns} dataSource={this.state.data} />
      <Button onClick={this.handleClick1}>加载本地数据1</Button>
      &nbsp;
      <Button onClick={this.handleClick2}>加载本地数据2</Button>
    </div>;
  }
});

ReactDOM.render(<App />
, document.getElementById('components-table-demo-local-data'));

由父元素控制自身数据展示。

import { Table } from 'antd';

function renderAction() {
  return <a href="#">删除</a>;
}

function expandedRowRender(record) {
  return <p>{record.description}</p>;
}

const columns = [
  {title: '姓名', dataIndex: 'name', key: 'name'},
  {title: '年龄', dataIndex: 'age', key: 'age'},
  {title: '住址', dataIndex: 'address', key: 'address'},
  {title: '操作', dataIndex: '', key: 'x', render: renderAction}
];

const data = [
  {key: 1, name: '胡彦斌', age: 32, address: '西湖区湖底公园1号', description: '我是胡彦斌,今年32岁,住在西湖区湖底公园1号。'},
  {key: 2, name: '吴彦祖', age: 42, address: '西湖区湖底公园2号', description: '我是吴彦祖,今年42岁,住在西湖区湖底公园2号。'},
  {key: 3, name: '李大嘴', age: 32, address: '西湖区湖底公园3号', description: '我是李大嘴,今年32岁,住在西湖区湖底公园3号。'}
];

ReactDOM.render(
<Table columns={columns}
  expandedRowRender={expandedRowRender}
  dataSource={data}
  className="table" />
, document.getElementById('components-table-demo-expand'));

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

import { Table } from 'antd';

// 事例表中第四行合并了五列,除了第一列设置 colSpan = 5 外
// 其他列的第四行 colSpan = 0 (被合并掉,不会渲染)
const renderContent = function(value, row, index) {
  let obj = {
    children: value,
    props: {}
  };
  if (index === 4) {
    obj.props.colSpan = 0;
  }
  return obj;
};

const columns = [{
  title: '姓名',
  dataIndex: 'name',
  render: function(text, row, index) {
    if (index < 4) {
      return <a href="#">{text}</a>;
    } else {
      return {
        children: <a href="#">{text}</a>,
        props: {
          colSpan: 5
        }
      };
    }
  }
}, {
  title: '年龄',
  dataIndex: 'age',
  render: renderContent
}, {
  title: '家庭电话',
  colSpan: 2,
  dataIndex: 'tel',
  render: function(value, row, index) {
    let obj = {
      children: value,
      props:{}
    };
    // 第三列的第三行行合并
    if (index === 2) {
      obj.props.rowSpan = 2;
    }

    // 第三列的第四行被合并没了,设置 rowSpan = 0 直接不用渲染
    if (index === 3) {
      obj.props.rowSpan = 0;
    }

    if (index === 4) {
      obj.props.colSpan = 0;
    }
    return obj;
  }
}, {
  title: '手机号',
  colSpan: 0,
  dataIndex: 'phone',
  render: renderContent
}, {
  title: '住址',
  dataIndex: 'address',
  render: renderContent
}];

const data = [{
  key: '1',
  name: '胡彦斌',
  age: 32,
  tel: '0571-22098909',
  phone: 18889898989,
  address: '西湖区湖底公园1号'
}, {
  key: '2',
  name: '胡彦祖',
  tel: '0571-22098333',
  phone: 18889898888,
  age: 42,
  address: '西湖区湖底公园1号'
}, {
  key: '3',
  name: '李大嘴',
  age: 32,
  tel: '0575-22098909',
  phone: 18900010002,
  address: '西湖区湖底公园1号'
}, {
  key: '4',
  name: '李夫人',
  age: 18,
  tel: '0575-22098909',
  phone: 18900010002,
  address: '西湖区湖底公园1号'
}, {
  key: '5',
  name: '习大大',
  age: 18,
  tel: '0575-22098909',
  phone: 18900010002,
  address: '西湖区湖底公园1号'
}];

ReactDOM.render(<Table columns={columns} dataSource={data} bordered />
, document.getElementById('components-table-demo-colspan-rowspan'));

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

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

import { Table, Button } from 'antd';

const columns = [{
  title: '姓名',
  dataIndex: 'name'
}, {
  title: '年龄',
  dataIndex: 'age'
}, {
  title: '住址',
  dataIndex: 'address'
}];
const data = [{
  key: '1',
  name: '胡彦斌',
  age: 32,
  address: '西湖区湖底公园1号'
}, {
  key: '2',
  name: '胡彦祖',
  age: 42,
  address: '西湖区湖底公园1号'
}, {
  key: '3',
  name: '李大嘴',
  age: 32,
  address: '西湖区湖底公园1号'
}];

const App = React.createClass({
  getInitialState() {
    return {
      loading: false
    };
  },
  toggleLoading() {
    this.setState({
      loading: !this.state.loading
    });
  },
  render() {
    return <div>
      <Table columns={columns} dataSource={data} loading={this.state.loading} />
      <Button type="primary" onClick={this.toggleLoading}>切换 loading 状态</Button>
    </div>;
  }
});

ReactDOM.render(<App />, document.getElementById('components-table-demo-loading'));

用属性 loading 控制表格加载中状态。