企业级的大数据量的 UI 展现,后台管理系统的3个核心场景
- Table 表格:支持虚拟滚动、列锁定、树状展开、内置的排序和过滤支持等
- Tree 树:动态加载节点数据、支持节点的拖拽、支持节点多选框、可内置于下拉框等等。
- Form 表单:表单状态管理、布局系统、丰富的验证机制等功能。可以一站式满足表单的开发需求
让不懂技术的人也能够参与开发,比如产品和运营,设计
ToB端页面,大部分界面以表格进行展示,以承载数据信息为主;结构简单,分类清晰,便于用户浏览
中后台业务表单很多也很重要,沉淀思考方案,把这个议题聊透,汇总出一个 Table的最佳实践。
帮助用户快速了解并简单分析信息的差异性、关联性等
整体界面需要复用度高,拓展性强
表格中各列内容相对独立,可根据业务需求或用户关注点的不同进行自定义调整
使用一种前端框架进行开发,保持视觉和代码一致性
数据量大,承载了核心业务
逻辑交互复杂,质量失控
拖拽生成表单、复杂自定义组件,输出 schema
快速配置简洁 JSON,输出基于 Antd 的表单
抽象出一些业务常见的表单
表格特性
常见的表格功能
- 固定表头 Header
- sticky 固定左侧列
- 固定右侧列
- 行选择
- 调整样式,如列颜色等
- 点击触发操作(比如加载数据)
- 导出当前数据
- 按列过滤
- 搜索
- 排序
- 表头上下箭头排序
- 拖拽行排序,交换行顺序
- 拖拽列顺序
- 绑定和展示数据(比如 http 查询的结果数据)
- 表格编辑
- 重命名列
- 编辑当前行
- 分页
表格技术点
- 大数据筛选排序优化
- Canvas Grid
- canvas实现 Table,性能好
- 快速滚动不会白屏,按照帧渲染
- DOM,AgGrid
- div实现 Table,容易定制开发
- 对象池技术
- 虚拟DOM,快速滚动会白屏
- 多人协同
table4.x
不支持的组合 expandedRowRender和column.fixed
展开行不能喝固定列一起使用
展开行不应该和固定列产生冲突。展开行作为单独一列,也应该独立于滚动之外。
从性能考虑,展开行只有在用户点击展开的时候进行渲染。这就导致了我们无法知道是否渲染方法会返回空元素。
v4 添加 rowExpandable 属性,用于判断是否该行支持展开;展开相关属性全部收入 expandable 属性中。
Table 的背景颜色正式提出了一个 @table-bg 变量
在 v4 中,Table 会跟随该变量设置背景色,不会再出现背景被透出来的情况。
v4 全选当前页”替换成了“选择全部”。并将他们抽取成 Table 的两个静态属性。便于用户选择与组合:
v4 templateAreas 属性简化用户的布局设置:
templateAreas: [[name, address, address],[name, building, no]]
v4 summary 属性支持。在 Table 中直接渲染一个 tfoot:用作总结栏
表格导出参考
https://github.com/adazzle/react-data-grid
table3.x
在 v3 中,左侧固定列和右侧固定列分别是两个独立的 Table:
position: sticky 实现元素相对于容器的粘附效果
rc-resize-observer ,通过 ResizeObserver 我们获得了更好的性能以及更准确的监听时机。hideDefaultSelections 的属性。会在勾选框边上添加一个下拉框并提供两个默认功能:
- 全选当前页面
- 反选当前页面
- 单独开启这个属性并不会有效果,它还有一个条件是你得设置
selections且至少有一个选项
table4.x封装
import React, { memo } from 'react'import { Table } from 'antd'function TableList (props) {const {className='',thead,data,rowKey = 'id',type,loading,scroll,selected,pagination, // 默认带分页rowChange, // null 没有单选或者复选列paginationChange, // 分页改变children} = props// rowSelection 选择表格行const rowSelection = {type, // 默认 'checkbox'selectedRowKeys: selected, // 默认选中的onChange: (rowKey, row) => rowChange({ rowKey, row }),getCheckboxProps: record => {return {defaultChecked: record.id === 1, // 默认选中的disabled: record.id === 4, // 禁用的}},}// 分页function setPagination(data, paginationChange) {const {current,pageSize,total,count,} = datareturn {current,pageSize,total,showQuickJumper: true,onShowSizeChange(page, limit) {console.log('show size', page, limit) // 向父级传递事件paginationChange && paginationChange({ page, limit })},onChange(page, limit) {console.log('page', page, limit)paginationChange && paginationChange({ page, limit })},showTotal() {return `共有 ${count} 条`}}}// 设置行属性function onRow ({record, index}) {return {onClick: event => {// console.log('onrow', record, index)}, // 点击行onDoubleClick: event => {console.log('onrow', record, index)},onContextMenu: event => { },onMouseEnter: event => { }, // 鼠标移入行onMouseLeave: event => { },}}const attr = {className,rowKey,loading,scroll,bordered: true,columns: thead,dataSource: tbody,pagination: pagination && setPagination(data, paginationChange),rowSelection: type && rowSelection,}return (<section><TableonRow={ (record, index) => onRow({record, index}) }/>{ children }</section>)}export default TableList
onChange
fucntion App() {const onChange = (pagination, filters, sorter) => {// const { current, pageSize } = paginationconsole.log(pagination, filters, sorter)}return (<TableonChange={onChange}/>)}
pagination, filters
pagination
import { injectIntl } from 'react-intl';@injectIntlclass App extends PureComponent {}const { intl, onChange} = this.props;const attr = {rowKey: 'id',loading,dataSource,columns,pagination: {total,current,pageSize,showSizeChanger: true,showQuickJumper: true,showTotal: value => intl.formatMessage(locales.total, { value }),// value 是搜索框的值onShowSizeChange: (current, pageSize) => onChange({ value, current, pageSize }),onChange: (current, pageSize) => onChange({ value, current, pageSize }),},};
hooks onChange优化
import { FormattedMessage } from 'react-intl';function TableList() {const attr = {rowKey: 'id',loading,columns,dataSource,onChange: (pagination, filters) => {const { current, pageSize } = paginationprops.onChange({ value, current, pageSize, filters });},pagination: {total,current,pageSize,showSizeChanger: true,showQuickJumper: true,showTotal: value =><FormattedMessage id="table.pagination.total" values={{ value }} />,},};return <Table {...attr} />}memo的用法function propsEqual(prevProps, nextProps) {// true 不会触发 render,false会重新 renderreturn JSON.stringify(prevProps.value) === JSON.stringify(nextProps.value)}export default memo(TableList, propsEqual);
