React 实现一个漂亮的 Table

概述

对于企业级后台产品来说,Table 应该是使用最频繁的组件了,它通常比 Form 和 Chart 的使用还频繁。对于这么一个常用的组件,我们决定要把它从 RSuite 中单独出来开发,并且要具有一定的通用性,适应很多场景。 首先看一下,Table 完成的效果。

  • 预览地址: https://rsuitejs.com/rsuite-table
  • Github: https://github.com/rsuite/rsuite-table

最开始促使我们去实现这个 Table 组件是因为产品经理希望表格可以像 Excel一样固定表头和列,我们都知道 HTML table 是不支持这个功能,但是在实际应用中,对于数据行多列多的情况下,固定表头和列非常有用,方便数据关联浏览。我们的组件库都是 React 的, 开源环境中也没有找到一个适合的我们的 Table 组件。 Ant Design 中的 Table 估计有些人用过,UI 比较漂亮,但是在固定表头和列的这个功能上我还是有些不满意,特别是要同时固定表头和列的时候,在 Retina 屏幕上,Ant Table 通过触摸板滚动表格,固定的区域和非固定的区域会对不整齐,看上去会抖动,这个体验不是特别好。我知道 facebook 的 FixedDataTable 针对这块的处理做的还不错,是一个好的参考,特别是大数据量渲染也不卡顿,但是有些功能也不能满足我们的业务场景,比如在要 Table 中呈现一个树形结构就没有这个功能。所以还是决定自己造这个轮子。

设计

在 UI 的设计上符合 RSuite 的整体风格。 我们具体看一下组件的设计,整个 Table 提供了 5 个组件,分别是:

  • <Table>定义表格,可以设置数据源,表格类型等等
  • <Column>定义列,设置列与数据源关联的 key,设置列宽度,设置是否可以排序,是否需要固定列等等。
  • <Cell>定义单元格,用于渲染数据的组件,可以自定义显示的方式。
  • <HeaderCell>定义列头的单元格。
  • <TablePagination>定义分页,是一个可选组件。

看一个简单的示例:

npm i rsuite rsuite-table --save 

有些地方依赖了 RSuite 中的基础组件,所有需要安装rsuite

import { Table,Column,HeaderCell,Cell } from 'rsuite-table'; 
<Table data={data} > <Column width={100} sort fixed resizable> <HeaderCell>ID</HeaderCell> <Cell dataKey="id" /> </Column> <Column width={100} sort resizable> <HeaderCell>Name</HeaderCell> <Cell dataKey="name" /> </Column> <Column width={100} sort resizable> <HeaderCell>Email</HeaderCell> <Cell dataKey="email" /> </Column> </Table> 

这是一个简单 3 列的表格,接下来我们来看一下具体的功能点。

功能介绍

锁定列

表头是默认固定的不需要额外的配置,要固定列,需要在固定的列&lt;Column>添加fixed属性。

<Column width={100} fixed> <HeaderCell>ID</HeaderCell> <Cell dataKey="id" /> </Column> 

这个功能是所有功能里面最麻烦的,特别是表头和列同时固定的时候,前面我也提到过 Ant Design 的 Table 就存在一个问题,滚动的时候固定列和非固定列未对齐,以下是一个 Ant Design 的 Table 的一个截图和访问链接。

访问地址: https://ant.design/components/table-cn/#components-table-demo-fixed-columns-header

造成这个问题的主要原因是onScroll触发的频率和渲染的速度跟不上造成的,如果要列和表头都固定,那必然会在一个方向上需要手动修改元素的位置,这里肯定不能用 React state 存储位置,然后等待渲染,那太慢了。所有需要操作 DOM,去改变元素的位置,这里有这几个需要注意的技术点:

  • 用 transform: translate3D 代替 top 与 left ,因为 top/left 会导致回流,而 translate 只产生重绘,性能会更好,另外 translate3D 走的是 3D,在手机浏览器器上会 GPU 加速。
  • onScroll触发的频率和渲染的速度会存在跟不上的情况,所有这里最好是自己实现一个滚动条,在 Table Body 上监听onWheel事件,在滚动条上监听onMouse*事件。 在自己实现滚动条的时候需要注意的是,在 Mac 的 chrome 上,左右滑动的时候会触发浏览器的上一页和下一页功能,所以这里的事件冒泡要处理好(本来想找一个开源的滚动条轮子,发现有好多组件这个问题没有处理好,所以就自己写了)。

对 DOM 操作用到了dom-lib

我们的 Table 在处理上面两点以后,就解决了 Ant Design 的 Table 滚动存在的问题,当然如果大家有更好的方案,感谢你分享一下。 另外,Ant Table 有很多方面做得是比我们好的,比如它支持固定右侧的列,支持嵌套表格等等功能。

完整示例代码

可调整列宽

在表格中有些列的数据有长有短,不太好预测,但还是希望在一个单元格内显示,如果给列固定好一个宽度以后,那超出单元格的内容就会被截断隐藏,导致信息显示不完整。Excel 的列是可以调整宽度的,所以我们也希望列可以调整宽度,只需要在&lt;Column>设置一个resizable属性。

<Column width={130} sortable> <HeaderCell>First Name</HeaderCell> <Cell dataKey="firstName" /> </Column> 

完整示例代码

自动设置列宽

有一种情况,Table 在页面中的宽度比如是1000px+ (可能更宽,根据显示器屏幕的宽度决定),但是这个 Table 只有 3 列,如果每列都固定一个200px, 肯定 撑不满整个 Table,导致不美观, 我们都知道 HTML table,当给 table 设置width:100%以后,列会根据内容自动撑满,如果给其中一个 td 设置了width,那 Table 剩下的 width,会被剩下的几列撑满。那在 rsuite-table 怎么解决问题呢? 看以下示例:

<Table width={1000}> <Column width={100}> <HeaderCell>First Name</HeaderCell> <Cell dataKey="firstName" /> </Column> <Column flexGrow={1}> <HeaderCell>City</HeaderCell> <Cell dataKey="city" /> </Column> <Column flexGrow={2}> <HeaderCell>Company Name</HeaderCell> <Cell dataKey="companyName" /> </Column> </Table> 

&lt;Column>组件上提供了一个flexGrow属性,有点类似 CSS3 中的flex-grow属性。上面示例中,Table 的width1000,第一列的width:100,第二列设置为flexGrow:1,第三列设置为flexGrow:2。 渲染后计算的结果是:

  • 第一列:100px
  • 第二列:flexGrow:1,(1000 - 100)/(2 + 1) * 1=300px
  • 第三列:flexGrow:2,(1000 - 100)/(2 + 1) * 2=600px

完整示例代码

排序

排序是一个基础的功能,在需要排序的列&lt;Column>设置一个sortable属性。 同时在&lt;Table>定义一个onSortColumn:Function回调函数,点击列头排序图标的时候,会触发该方法,并返回sortColumn:StringsortType:String('asc'|desc)。 看一下示例:

<Table onSortColumn={(sortColumn, sortType)=>{ console.log(sortColumn,sortType); }} > <Column width={50} sortable> <HeaderCell>Id</HeaderCell> <Cell dataKey="id" /> </Column> <Column width={130} sortable > <HeaderCell>First Name</HeaderCell> <Cell dataKey="firstName" /> </Column> <!--... --> </Table> 

完整示例代码

分页

提供了一个&lt;TablePagination>组件,用于显示分页栏,这里的分页需要开发人员自己去处理数据,看一下示例代码:

function formatLengthMenu(lengthMenu) { return ( <div className="table-length"> <span> 每页 </span> {lengthMenu} <span></span> </div> ); } function formatInfo(total,activePage) { return ( <span><i>{total}</i> 条数据</span> ); } 
<TablePagination formatLengthMenu={formatLengthMenu} formatInfo={formatInfo} displayLength={100} total={500} onChangePage={this.handleChangePage} onChangeLength={this.handleChangeLength} /> 
  • formatLengthMenu 格式化显示行数;
  • formatInfo 格式化显示总条目信息;
  • displayLength 默认显示多少行数据,可以通过 state 管理;
  • total 它不是当前返回数据的行数,他是所有数据的总条目数,这个需要后端 API 的返回,通过这个值与displayLength,才能计算出表格分多少页。可以通过 state 管理;
  • onChangePage 切换分页的回调函数;
  • onChangeLength 切换显示条目数的回调函数。

看一下,效果:

完整示例代码

树形表格

先看一下树形表格的样子

渲染成树形的表格需要设置两个地方,首先&lt;Table>组件上设置一个isTree属性,同时data中的数据需要通过children来定义关系结构。

<Table data={data} isTree expand height={400}> 

data 中的数据结构

[{
    labelName: '汽车',status: 'ENABLED',children: [ { labelName: '梅赛德斯-奔驰',count: 460 } ... ] ... }] 

完整示例代码

自定义单元格

单元格中的内容往往需要能交互的,比如设置为一个连接,或者hover的时候能显示一段信息等等。 在 rsuite-table 中,可以对Cell进行自定义。 先看一下以下是一个自定义后的表格图例:

比如,显示一个图片,定义一个ImageCell组件:

const ImageCell = ({ rowData,dataKey,...props }) => ( <Cell {...props}> <img src={rowData[dataKey]} width="50" /> </Cell> ); 

用的时候:

<Column width={200} > <HeaderCell>Avartar</HeaderCell> <ImageCell dataKey="avartar" /> </Column> 

比如,要格式化日期,就定义一个DateCell组件:

const DateCell = ({ rowData,...props }) => ( <Cell {...props}> {rowData[dataKey].toLocaleString()} </Cell> ); 

用的时候:

<Column width={200} > <HeaderCell>Action</HeaderCell> <DateCell dataKey="date" /> </Column> 

自定义行高

如果在实际应用中需要根据数据内容来定义行高,可以使用以下方式

<Table onRerenderRowHeight={(rowData) => { if (rowData.firstName === 'Janis') { return 30; } }} > ... </Table> 

完整示例代码

可编辑的表格

可编辑的表格,只需要自定义一个&lt;Cell>,然后通过state管理状态。

export const EditCell = ({ rowData,onChange,...props }) => { return ( <Cell {...props}> {rowData.status === 'EDIT' ? ( <input className="input" defaultValue={rowData[dataKey]} onChange={(event) => { onChange && onChange(rowData.id,event.target.value); }} /> ) : rowData[dataKey]} </Cell> ); }; 

完整示例代码

遗留的问题

  • 内容自动换行,并且自动设置行高,在 HTML table 中很容易实现这个功能,如果整个&lt;Table>是的通过 CSS 布局控制也许能实现这个功能,但是在实现的时候,很多地方都是通过 JS 控制高度,比如: 行高、单元格的 left,top 相对位置等等,所以要根据内容来自动行高是比较麻烦的事情,暂时没想到好的解决办法,但是我们提供了一个onRerenderRowHeight函数,可以让用户自己根据内容来控制行高。
  • 根据内容自动设置列宽, 这个问题暂时也没有想到好的解决方案, 现在只能通过flexGrow来填充剩余宽度。
  • 固定列在右侧,这个功能后续会考虑加进去。
  • 表头分组,合并单元格,这个功能麻烦点在于,我们所有的列都是可以调整列宽的,如果同时考虑合并单元格逻辑上处理有些麻烦,不过后续会考虑加入该功能。

如果,你对这些问题有好的想法欢迎你 提交pull request。 如果,你在使用中存在任何问题,可以提交issues。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom