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 控制表格加载中状态。