一、前言
手写Table组件这个文章我一直都想写,今天终于得空来写它了。小编认为Table组件是组件库里”较为复杂”的一个组件,因为它的扩展性非常强,并且它的基础样式如何去写都非常考究,那么今天我就带大家来实现一个基础功能的table组件,废话不多bb,进入正题吧。
二、实现哪些功能?
- 最基础的table布局
- 固定头
- 固定列
三、实现的效果
以及组件的使用方式期望是这样:
const column = [
{ name: '序号', key: 'order', width: '100px', isFixed: true },
{ name: '第二列', key: 'col2', width: '200px' },
{ name: '第三列', key: 'col3', width: '300px' },
{ name: '第四列', key: 'col4', width: '200px' },
{ name: '第五列', key: 'col5', width: '500px' },
{ name: '第六列', key: 'col6', width: '200px' },
];
const data = [
{ order: 1, col2: '1-2', col3: '1-3', col4: '1-4', col5: '1-5', col6: '1-6' },
{ order: 2, col2: '2-2', col3: '2-3', col4: '2-4', col5: '2-5', col6: '2-6' },
{ order: 3, col2: '3-2', col3: '3-3', col4: '3-4', col5: '3-5', col6: '3-6' },
{ order: 4, col2: '4-2', col3: '4-3', col4: '4-4', col5: '4-5', col6: '4-6' },
{ order: 5, col2: '5-2', col3: '5-3', col4: '5-4', col5: '5-5', col6: '5-6' }
];
<CustomTable column = { column } data = { data } rowHeight = { '100px' } />
四、动手实现
4.1、table组件的尺寸
4.1.1、如何定义table组件的宽?
对于table组件来说,定义宽度有2种方式,一种是”固定宽度布局”,另一种是”自由宽度布局”。我们可以通过”table-layout”属性在这2种方式中做出选择。
4.1.1.1、“固定宽度布局”
就是显示的定义table标签的宽度(即table-layout: fixed),那么此时
<table>
的宽度 与 各列[td | th]
的宽度的关系如下:
- table真实的width = Math.max(table.style.width, 各列宽度之和)
- 如果 table.style.width > 各列宽度之和,那么
二者之差再除以列数
得到的结果将增加到各列上。
纸上得来终觉浅,我们来跑下代码加深一下上述节论的记忆(以下均是react环境下的代码
):
table {
table-layout: fixed;
border-collapse: collapse;
border-spacing: 0px;
width: 200px;
}
<div className = 'yon-table-box'>
<table>
<colgroup>
<col style={{ width: '100px' }}>
<col style={{ width: '100px' }}>
<col style={{ width: '100px' }}>
<colgroup>
<thead>
<th>列1</th>
<th>列2</th>
<th>列3</th>
</thead>
</table>
</div>
此时效果如下:
此时我们再稍微改动下样式,css如下:
table {
table-layout: fixed;
width: 500px;
border-collapse: collspse;
border-spacing: 0px;
}
效果如下:
此时我们发现,上述结论是对的。
4.1.1.2、”自由宽度布局”
顾名思义,更容易实现这个等式 “table.style.width == 各列宽度之和 + 边框 + 间距”。通过设置 table-layout: auto 来触发。
我们来跑下代码试一下:
table {
table-layout: auto;
border-collapse: collapse;
border-spacing: 0px;
width: auto;
}
<div className = 'table-box'>
<table>
<colgroup>
<col style={{ width: '100px' }}>
<col style={{ width: '100px' }}>
<col style={{ width: '100px' }}>
</colgroup>
<thead>
<th>列1</th>
<th>列2</th>
<th>列3</th>
</thead>
</table>
</div>
此时我们来看下效果:
4.1.1.3、table宽度的最终选择
经过上面2种布局的分析,我们决定选用”自由宽度布局”的方式定义table的宽度,此时咱们的CustomTable组件的代码如下:
.yon-table-box {
width: 100px;
height: 100px;
overflow: scroll;
}
table {
table-layout: auto;
table-collapse: collapse;
table-spacing: 0px;
width: auto;
}
th {
background: #f4f4f4;
}
import React from 'react';
class CustomTable extends React.Component {
constructor(props){
super(props)
}
render (){
const { column } = this.props;
return <div className = 'yon-table-box'>
<div className = 'right'>
<table>
<colgroup>
{
column.map(colItem => {
return <col style={{ colItem.width }}>
})
}
</colgroup>
<thead>
<tr>
{
column.map(colItem => {
return <th>{ colItem.name }</th>
})
}
</tr>
</thead>
</table>
</div>
</div>
}
}
Table的使用:
import React from 'react';
const column = [
{ name: '列1', key: 'col1', width: '100px' },
{ name: '列2', key: 'col2', width: '200px' },
{ name: '列3', key: 'col3', width: '300px' },
];
<CustomTable column = { column } />
4.1.2、如何定义table组件的高?
定义table组件的行高度,这个就比较简单了,我们只需要保证 table.style.height = ‘auto’, 即可实现自定义行高,CustomTable组件的代码如下:
.yon-table-box {
width: 100px;
height: 100px;
overflow: scroll;
}
table {
table-layout: auto;
table-collapse: collapse;
table-spacing: 0px;
width: auto;
// 新增代码+++++++++++++++++++++
height: auto;
}
th {
background: #f4f4f4;
}
// 新增代码++++++++++++++++++++++++++
td {
background: #fff;
border-bottom: 1px solid rgb(208, 208, 208);
}
import React from 'react';
class CustomTable extends React.Component {
constructor(props){
super(props)
}
render (){
const { column, data, rowHeight } = this.props;
return <div className = 'yon-table-box'>
<div className = 'right'>
<table>
<colgroup>
{
column.map(colItem => {
return <col style={{ colItem.width }}>
})
}
</colgroup>
<thead>
<tr>
{
column.map(colItem => {
return <th>{ colItem.name }</th>
})
}
</tr>
</thead>
<tbody>
{
// 新增代码+++++++++++++++++++++++++
data.map(dataItem => {
return <tr>
{
column.map(colItem => {
return <td style={{ height: rowHeight }}>{dataItem[colItem.key]}</td>
})
}
</tr>
})
}
</tbody>
</table>
</div>
</div>
}
}
好啦,此时咱们的第一个功能到这就算实现啦,如果你是一步一步跟下来的,那么此时的效果应该是这样的:
4.2、固定列
我们先来看一下下面的图:
首先,根据目前实现的效果来看,我们可以得出以下信息:
- 目前dom上就只有一个table,意味着 表头、表体是连着的,符合正常文档流所有的特点。
- 红色框是我们想要固定的列。
好,此时我们来想一下能够做到固定列(无非是2个div互不干扰呗)的方法有哪些?
4.2.1、方式一
把一个table分为左右2个table,并保证只有右侧的table局部滚动
,干掉父div的scroll。
4.2.2、方式二
依旧是一个table分为左右2个table,只是在不改变现在滚动的情况下
,给左侧的table新增一个”sticky”的粘性定位即可。
4.2.3、方式三
利用js,监听scroll事件,在滚动的时候,利用transform属性,动态的设置translateX,来达到固定列的效果
4.2.3、固定列的最终选择
这里我们选择 方式二。理由非常简单,改动量非常少的情况就可以实现,其他的2种情况的改动都挺大,但是也可以实现,下面我会把其他的2种方式的伪代码写出来供大家参考。
- 方式一。其实这种方式更适合”固定宽度布局”的table,因为这种table的width是非常容易拿到的,你只需要保证列数过多的时候出现滚动条就可以(也仅仅是一行样式的问题),伪代码如下:
import React from 'react';
export default class App extends React.Component {
constructor(props){
super(props)
}
render (){
return <div style={{ display: 'flex' }}>
<!-- 左侧固定列 -->
<div className = 'left'>
<table></table>
</div>
<!-- 右侧自由列 -->
<div className = 'right' style={{ overflow: 'scroll' }}>
<table></table>
</div>
</div>
}
}
- 方式三。这种利用js的方式是最看重代码结构的,而且还会使你的代码变得非常臃肿(不利于拆分),它比较适用于下面的写法:
import React from 'react';
// 大家看到这样的写法其实可能感觉不到什么,但是如果这样写的话,table组件的抽取将会异常困难。
class App extends React.Component {
constructor(props){
super(props)
this.leftTableRef = React.createRef();
this.scrollTableBox = React.createRef();
}
scrollTable = () => {
// 保持左侧固定不动
this.leftTableRef.current.style.transform = `translateX(-${this.scrollTableBox.current.scrollLeft}`;
}
render (){
return <div ref = { this.scrollTableBox } style={{ display: 'flex', overflow: 'scroll' }} onScroll = { () => this.scrollTable() }>
<!-- 左侧固定列table -->
<table ref={ this.leftTableRef }></table>
<!-- 右侧自由列table -->
<table></table>
</div>
}
}
4.2.4、固定列的最终代码
.yon-table-box {
width: 100px;
height: 100px;
overflow: scroll;
}
table {
table-layout: auto;
table-collapse: collapse;
table-spacing: 0px;
width: auto;
// 新增代码+++++++++++++++++++++
height: auto;
}
th {
background: #f4f4f4;
}
// 新增代码++++++++++++++++++++++++++
td {
background: #fff;
border-bottom: 1px solid rgb(208, 208, 208);
}
// 新增代码++++++++++++++++++++++++++
.left {
position: sticky;
left: 0px;
}
import React from 'react';
class CustomTable extends React.Component {
constructor(props){
super(props)
this.state = {
// 固定列集合
fixedColumnArr : [];
// 自由列集合
notFixedColumnArr: [];
}
}
componentDidMount(){
const { column } = this.props;
this.setState(state => {
return {
// column配置项中,isFixed用于标识是否是固定列
fixedColumnArr : column.filter(item => item.isFixed),
notFixedColumnArr: column.filter(item => !item.isFixed)
}
})
}
render (){
const { data, column, rowHeight } = this.props;
const { fixedColumnArr, notFixedColumnArr } = this.state;
return <div className = 'yon-table-box'>
{
fixedColumnArr.length > 0 && <div className = 'left'>
<table>
<colgroup>
{
fixedColumnArr.map(item => {
return <col style={{ minWidth: item.width, width: item.width }}>
})
}
</colgroup>
<thead>
<tr>
{
fixedColumnArr.map(item => {
return <th>{item.name}</th>
})
}
</tr>
</thead>
<tbody>
{
data.map(dataItem => {
return <tr>
{
fixedColumnArr.map(colItem => {
return <td>{dataItem[colItem.key]}</td>
})
}
</tr>
})
}
</tbody>
</table>
</div>
}
<div className = 'right'>
<!-- 跟left代码一样,只需要把 fixedColumnArr 换成 notFixedColumnArr 即可 -->
</div>
</div>
}
}
4.3、固定头
固定头的实现思路 其实和 固定列的思路是一样的,这里就不具体分析了,夜也深了,哈哈哈,这里我就偷点懒,给伪代码了:
import React from 'react';
class CustomTable extends React.Component {
constructor(props){
super(props)
this.state = {
// 固定列集合
fixedColumnArr : [];
// 自由列集合
notFixedColumnArr: [];
}
}
render (){
const { data, column, rowHeight } = this.props;
return <div className = 'yon-table-box'>
// 整张表的头部(整个头部是需要sticky的)
<div className = 'top'>
<div className = 'left'></div>
<div className = 'right'></div>
</div>
// 整张表的body(左侧需要固定的body是需要sticky的)
<div className = 'bottom'>
<div className = 'left'></div>
<div className = 'right'></div>
</div>
</div>
}
}
五、最后
好啦,table组件这次就分享到这里了,文章里有讲的不对的地方,欢迎大家指正,如果大家对table组件的实现有什么好的想法,也欢迎评论留言,那么886~~~
暂无评论内容