Table
顾名思义就是一个表格元件,用来整齐的显示行列数据。
我自己觉得 table 是一个还蛮繁琐的元件,要组成一个 table 就需要各式各样的 tag,例如 table, thead, tbody, tr,td。
特别是当我们的 table 资料比较复杂的时候,程序码的结构也会跟着复杂起来,甚至会需要夹杂 JavaScript 的逻辑判断在里面,当程序码变得很难一眼看懂的时候,维护起来所要下的功夫也会随之增加。
<table>
<thead>
<tr>
<th colspan="2">The table header</th>
</tr>
</thead>
<tbody>
<tr>
<td>The table body</td>
<td>with two columns</td>
</tr>
</tbody>
</table>
因此有时候我们也可以拥有另一种选择,就是我们希望能够做一个元件,让我们避免每次都要撰写这些复杂的巢状结构,而是只要给定表格的栏位以及资料,这个元件就能够自动帮我们产生 Table。例如 Antd 的 Table 就是这样设计的:
import { Table } from 'antd';
<Table dataSource={dataSource} columns={columns} />;
MUI 虽然他有自己提供的一套 Table library 来让我们用类似原生 html 的方式来组装这些 Table 的巢状结构,但同样的他也有出一套 DataGrid
让我们能够只用栏位及资料来产生一个 Data Table:
import { DataGrid } from '@material-ui/data-grid';
<DataGrid rows={rows} columns={columns} />
用 Data 直接映射出 Table 的方式,虽然在功能和样式上有一些限制,但是这样的牺牲可以为我们带来维护上的好处,特别是我们网站上有许多的 Table,并且这些 Table 并不会差异太大的时候,或许可以考虑这样的方式,例如可能某个後台管理系统在不同的分页会需要类似的表格,会员管理表格、文章管理表格、订单管理表格......等等。
因此本篇中会演示如何做出一个简单的 Data table,并且选择一些我觉得可能会容易用到的属性来当范例。
columns
columns 这个属性用来描述表格栏位的配置,每一栏用一个物件来表示,其中,title 用来描述要显示的栏位名称;dataIndex 用来做与资料的对应;render 可以帮助我们在这一栏当中生成比较复杂的数据,例如这一栏当中需要显示 icon 或需要实现点击事件等等;width 用来指定栏位的宽度;align 用来设置栏位对齐的方式。
const columns = [
{
title: 'Name',
dataIndex: 'name',
width: '100px',
align: 'center',
render: ({ name }) => <a href="...">{name}</a>,
},
//... 其他栏位
];
dataSource
dataSource 用来指定表格的数据内容,一个 object 代表一列,以下面为例,会有 name, age, address 等资料。
const dataSource = [
{
key: '1',
name: '胡彦斌',
age: 32,
address: '西湖区湖底公园1号',
tags: ['nice', 'developer'],
},
{
key: '2',
name: '胡彦祖',
age: 42,
address: '西湖区湖底公园1号',
tags: ['cool', 'teacher'],
},
];
Table
属性 | 说明 | 类型 | 默认值 |
---|---|---|---|
columns | 描述表格栏位的配置 | ColumnsType[] | |
dataSource | 指定表格的数据内容 | object[] |
Columns
属性 | 说明 | 类型 | 默认值 |
---|---|---|---|
title | 栏位名称 | string | |
dataIndex | 用来对应数据 | string | |
width | 设置宽度 | string, number | |
align | 设置对齐方式 | left, right, center | |
render | 生成数据复杂的渲染 | (data) => {} |
按照上述的分析以及说明,我们可以开始 Data Table 的实作。
因为我们已经有了 columns
这个资料,所以我们可以把 table header 用迭代的方式产生出来:
const columns = [
{
title: 'Name',
dataIndex: 'name',
width: '100px',
align: 'center',
render: ({ name }) => <a href="...">{name}</a>,
},
//... 其他栏位
];
<table>
<thead>
<tr>
{
columns.map((column) => (
<th key={column.key}>
{column.title}
</th>
))
}
</tr>
</thead>
<tbody>...</tbody>
</table>
再来因为我们已经有了 dataSource ,也就是每一笔 row 的资料,所以我们也可以用迭代的方式把内容产生出来。
但这边会比 header 较复杂一点,我用了两层的回圈,最外层的回圈是一笔一笔的 row 的资料,而内层的回圈,是一笔 row 当中每个 column 的资料。
所以外层用 dataSource 来迭代,而内层用刚刚迭代出 header 的 columns 来迭代,因此程序码如下:
<tbody>
{
dataSource.map((data) => (
<tr key={data.key}>
{
columns.map((column) => {
const { dataIndex } = column;
const foundCellData = column.render
? column.render(data[dataIndex])
: data[dataIndex];
return (
<td key={column.key}>
{foundCellData}
</td>
);
})
}
</tr>
))
}
</tbody>
好啦,用以上的方式,我们就可以不用自己去写 table 的结构,直接从外面定义好 columns 以及 dataSource,就能够产生出一个 table 了!这样即使资料增加很多笔,我们程序码中的 table 也不会越来越大坨。
下面就是我们产生出的不带样式的 table:
但是这个 table 也不是真的完全不带样式啦,我至少有给他 border,然後有处理一下 border-collapse
的问题,border-collapse 属性的功能是用来将表格栏位边框合并,让表格变得更美化:
const StyledTable = styled.table`
border-collapse: collapse;
* {
border: 1px solid #000;
box-sizing: border-box;
}
`;
当然这个样式真的是太阳春,不过没关系,因为这个 table 是我们自己手刻的,所以也可以按照自己心意调整样式,这边我示范一个透过 styled-components 来客制化样式的例子,我以一个 Antd 样式的 table 为例:
const AntdStyle = styled(Table)`
width: 100%;
* {
border: none;
white-space: nowrap;
text-align: left;
}
th {
background: #fafafa;
}
td, th {
padding: 16px;
}
tr {
border-bottom: 1px solid #f0f0f0;
}
`;
成果如下图,简单几个 css 就能够让他看起来有模有样,而且我们的 props 传入介面也完全不会受到影响:
指定栏位宽度
我们也可以像 antd 一样,从 columns 资料结构当中,给他 width 的属性,让他可以指定那个栏位要多大的宽度,像这样:
const columns = [
{
title: 'Name',
dataIndex: 'name',
width: 130,
},
//... 其他栏位
];
而我们 table 的结构就能够根据这个 width 来调整我们栏位的宽度:
<thead>
<tr>
{
columns.map((column) => (
<th key={column.key} style={{ width: column.width }}>
{column.title}
</th>
))
}
</tr>
</thead>
Sticky column
我们萤幕不够宽,但是 table 很宽,栏位很多的时候,势必会需要 sticky column 的功能,先开门见山给大家看一下效果:
为了做到可以 scroll 的效果,我们必须要调整一下 table 元件的结构,我们要在 table 外面再包一层 div ,使得 div 容纳不下 table 的宽度的时候可以出现 scroll bar:
<div style={{ width: '100%', overflow: 'auto' }}>
<StyledTable
className={className}
$columnsCount={columns.length}
>
<thead>...</thead>
<tbody>...</tbody>
</StyledTable>
</div>
我们想要做到的效果是,当 columns 的资料里面有 fixed: true
的时候,我们要可以冻结住那一栏,像是下面这样:
const columns = [
{
title: 'Name',
dataIndex: 'name',
width: 130,
fixed: true,
},
//... 其他栏位
];
准备好 props 的资料之後,我们就要把 fixed 这个 props 传入元件中对应的节点,我们需要传入的节点是 thead 上面第一个 column 的 th
,以及 tbody 当中第一个 column 的 td
:
在 styled-components 当中拿到这个 fixed
之後我们来决定要不要让他可以冻结:
const Th = styled.th`
width: ${(props) => props.$width}px;
${(props) => props.$fixed && stickyLeftStyle};
`;
const Td = styled.td`
background: #FFF;
${(props) => props.$fixed && stickyLeftStyle};
`;
冻结的关键 CSS 在这边,我们用 position: sticky;
这个属性来帮助我们做到冻结,
const stickyLeftStyle = css`
position: sticky;
left: 0px;
z-index: 2;
/* ...(略) */
`;
到目前为止我们就能够做出一个没有阴影样式的 sticky column 效果了:
没有阴影或是 border 真的是很难看出栏位之间的边界,但是我们实际上动手做过就会知道,这边的 boder 或是要做阴影真的没有那麽直觉就能够做到,所以我去偷看了一下 Antd 的样式,学到了他的撇步:
const stickyLeftStyle = css`
position: sticky;
left: 0px;
z-index: 2;
&:after {
content: "";
position: absolute;
right: 0px;
top: 0px;
width: 30px;
height: 100%;
box-shadow: inset 10px 0 8px -8px #00000026;
transform: translateX(100%);
}
`;
这边的阴影并不是 column 自己本身的阴影,而是透过他的伪元素 ::after
来模拟阴影的效果,让 ::after
往右边外面延伸,并且给他阴影,让整个看起来很像是 column 自己的阴影:
客制化表格内容
当然我们要让表格内容除了能够显示文字之外,我们也能够支援其他的内容,例如下面我们能够放入删除按钮
,我先做一个很阳春的样式来示意:
要怎麽做到这件事呢?我们看一下 Antd 介面上是怎麽设计,他是透过在 column 资料结构里面定义一个 render 的属性,他是一个 function ,可以帮助我们在指定的 cell 当中 render 出我们期待的内容:
const columns = [
//... 其他栏位
{
title: '操作',
dataIndex: 'actions',
key: 'actions',
render: () => (
<Button themeColor="secondary">
<span>删除</span>
</Button>
),
},
];
在我们的 table 元件当中,当然就是判断有没有这个 render 的栏位,如果有的话就呼叫他,把内容画出来,如果没有的话,就显示预设的文字:
以上就是我演示的简易 Data table,当然要把一个 table 做好,还有许多细节需要注意,也有许多功能值得我们扩充,但是不见得每个功能我们都会需要,因此大家按照自己的专案的需求来调整就可以了。
Table 元件原始码:
Source code
Storybook:
Table
>>: Day 14— To Do List (1) 专案前置
目标 主题是【从资料库到资料分析视觉化】, 希望可以更深入的了解data, 从资料库的架构,资料的...
今天谈到最常用的函式 function 一般来说,函式的定义方式如图中所示 name: 代表函式的名...
使用GCP部署机器学习API 此范例使用鸢尾花朵资料集进行 XGBoost 分类器模型训练。将模型储...
前言 JavaScript 相较是个自由的语言,在学习语法时会发现,咦 明明规则是这样,怎麽那样也可...
今天这个,真的是插班车,因为今天作完弱扫,总共八份的测报。 我自已看得都要吐了。 在思考,这个月,因...