建设视频网站设计意义,邢台网站推广专业服务,店铺网站平台建设方案,深圳网站制作品牌祥奔科技前言
在移动端开发中#xff0c;表格组件是一个常见但复杂的需求。相比PC端#xff0c;移动端表格面临着屏幕空间有限、交互方式不同、性能要求更高等挑战。本文将详细介绍如何从零开始构建一个功能完整的移动端React表格组件#xff0c;包含固定列、智能单元格合并、排序等…前言
在移动端开发中表格组件是一个常见但复杂的需求。相比PC端移动端表格面临着屏幕空间有限、交互方式不同、性能要求更高等挑战。本文将详细介绍如何从零开始构建一个功能完整的移动端React表格组件包含固定列、智能单元格合并、排序等高级功能。
项目背景
在实际项目中我们经常遇到以下痛点
现有表格组件在移动端体验不佳复杂的单元格合并需求难以实现固定列在不同屏幕尺寸下对齐问题大数据量下的性能优化
基于这些需求我们开发了 wtechtec/mobile-table 组件库。
技术栈选择
React 18 - 主框架TypeScript - 类型安全NutUI React - 基础UI组件库Rollup - 构建工具PostCSS - 样式处理
核心功能设计
1. 基础表格结构
首先定义表格的基础类型
export interface BasicTableProps extends BasicComponent {columns: ArrayTableColumnPropsdata: Arrayanybordered: booleansummary?: React.ReactNodestriped?: booleannoData?: React.ReactNodesorterIcon?: React.ReactNodeonSort?: (column: TableColumnProps, sortedData: Arrayany) voidshowHeader?: boolean
}export interface TableColumnProps {key: stringtitle?: stringalign?: stringsorter?: ((a: any, b: any) number) | boolean | stringrender?: (rowData: any, rowIndex: number) string | React.ReactNodefixed?: left | rightwidth?: numberonCell?: (rowData: any, rowIndex: number) CellConfig
}2. 固定列实现原理
固定列是移动端表格的核心功能实现思路如下
// 计算固定列宽度
const useTableSticky (columns: TableColumnProps[], rtl: boolean) {const [stickyLeftWidth, setStickyLeftWidth] useState(0)const [stickyRightWidth, setStickyRightWidth] useState(0)useEffect(() {// 计算左固定列总宽度let leftWidth 0let rightWidth 0columns.forEach(col {if (col.fixed left col.width) {leftWidth col.width}if (col.fixed right col.width) {rightWidth col.width}})setStickyLeftWidth(leftWidth)setStickyRightWidth(rightWidth)}, [columns])return { stickyLeftWidth, stickyRightWidth }
}关键点以实际渲染宽度为准
在实际开发中我们发现设置的 width 和渲染出来的宽度可能不一致因此采用动态获取DOM宽度的方案
useEffect(() {// 获取所有 fixed: left 列的实际宽度let width 0columns.forEach(col {if (col.fixed left thRefs.current[col.key]) {width thRefs.current[col.key]!.offsetWidth}})setStickyLeftWidth(width)
}, [columns, data])3. 智能单元格合并算法
这是本组件的亮点功能能够自动识别相同值并进行最优的矩形区域合并
// 创建多行多列合并配置
export const createMultiRowColumnMergeCellConfig (data: any[], columns: string[]) {const mergeCellMap new Mapstring, { rowSpan: number; colSpan: number; isMainCell: boolean;value: any;mergeType: row | column | block;}()// 创建值到位置的映射const valueToPositions new Mapany, Array{row: number, col: number, colKey: string}()// 收集所有相同值的位置data.forEach((item, rowIndex) {columns.forEach((colKey, colIndex) {const value item[colKey]if (value ! null value ! undefined value ! ) {if (!valueToPositions.has(value)) {valueToPositions.set(value, [])}valueToPositions.get(value)!.push({row: rowIndex,col: colIndex,colKey})}})})// 处理每个相同值的合并valueToPositions.forEach((positions, value) {if (positions.length 1) returnconst mergeAreas findMaxRectangleAreas(positions)mergeAreas.forEach(area {if (area.positions.length 1) {createMergeArea(area, value, mergeCellMap)}})})return mergeCellMap
}矩形区域识别算法
// 查找最大矩形合并区域
const findMaxRectangleAreas (positions: Array{row: number, col: number, colKey: string}) {const areas []const usedPositions new Setstring()const sortedPositions [...positions].sort((a, b) {if (a.row ! b.row) return a.row - b.rowreturn a.col - b.col})for (const startPos of sortedPositions) {const startKey ${startPos.row}-${startPos.col}if (usedPositions.has(startKey)) continue// 尝试找到以当前位置为起点的最大矩形const maxRect findLargestRectangleFromPosition(startPos, positions, usedPositions)if (maxRect.positions.length 1) {areas.push(maxRect)maxRect.positions.forEach(pos {usedPositions.add(${pos.row}-${pos.col})})}}return areas
}4. 排序功能实现
const handleSorterClick (item: TableColumnProps) {if (item.sorter !sortedMapping.current[item.key]) {const copied [...innerValue]if (typeof item.sorter function) {copied.sort(item.sorter as (a: any, b: any) number)} else if (item.sorter default) {copied.sort()}sortedMapping.current[item.key] truesetValue(copied, true)onSort onSort(item, copied)} else {sortedMapping.current[item.key] falsesetValue(data)}
}样式设计与优化
1. 移动端适配
.nut-table {overflow: hidden;position: relative;word-wrap: break-word;word-break: break-all;
}.nut-table-wrapper {display: flex;width: 100%;flex-direction: column;font-size: 14px;color: #1a1a1a;overflow-y: auto;overflow-x: hidden;position: relative;border: 1px solid #f0f0f0;
}.nut-table-wrapper-sticky {overflow-x: auto;
}2. 固定列样式
.nut-table-fixed-left,
.nut-table-fixed-right {position: sticky;z-index: 2;
}.nut-table-sticky-left {left: 1px;box-shadow: 6px 0 6px -4px rgba(0, 0, 0, 0.15);
}.nut-table-sticky-right {right: 1px;box-shadow: -6px 0 6px -4px rgba(0, 0, 0, 0.15);
}构建配置优化
Rollup 配置
export default {input: src/index.ts,output: [{file: pkg.main,format: cjs,sourcemap: true,exports: named},{file: pkg.module,format: esm,sourcemap: true,exports: named},{file: pkg.unpkg,format: umd,name: MobileTable}],external: [react, react-dom, nutui/nutui-react],plugins: [resolve({extensions: [.ts, .tsx, .js, .jsx],preferBuiltins: false,dedupe: [react, react-dom]}),postcss({inject: true,extract: false,modules: false // 关键禁用CSS模块化}),typescript({tsconfig: ./tsconfig.json,declaration: true,declarationDir: dist,rootDir: src})]
}使用示例
基础用法
import { Table } from wtechtec/mobile-tableconst columns [{ key: name, title: 姓名, width: 100, fixed: left },{ key: age, title: 年龄, width: 80 },{ key: address, title: 地址, width: 200 }
]const data [{ name: 张三, age: 25, address: 北京市朝阳区 },{ name: 李四, age: 30, address: 上海市浦东新区 }
]Table columns{columns} data{data} /智能合并用法
import { Table, createMultiMergeOnCellFunction, createMultiRowColumnMergeCellConfig
} from wtechtec/mobile-tableconst mergeColumns [gender, age, class]
const multiMergeCellMap createMultiRowColumnMergeCellConfig(data, mergeColumns)const columns [{key: gender,title: 性别,onCell: createMultiMergeOnCellFunction(multiMergeCellMap, gender)}// ...
]性能优化策略
1. 虚拟滚动大数据量
const VirtualTable ({ data, height 400 }) {const [startIndex, setStartIndex] useState(0)const [endIndex, setEndIndex] useState(20)const visibleData useMemo(() {return data.slice(startIndex, endIndex)}, [data, startIndex, endIndex])return Table data{visibleData} /
}2. 合并计算缓存
const useMergeCellMap (data: any[], columns: string[]) {return useMemo(() {return createMultiRowColumnMergeCellConfig(data, columns)}, [data, columns])
}遇到的技术难点与解决方案
1. CSS样式无效问题
问题npm包引用后样式无效
原因Rollup配置中开启了CSS模块化导致类名被哈希化
解决方案
postcss({inject: true,extract: false,modules: false // 禁用CSS模块化
})2. 固定列对齐问题
问题设置的width与实际渲染宽度不一致
解决方案以实际DOM宽度为准动态计算sticky区域宽度
3. 单元格合并复杂度
问题如何实现智能的多行多列合并
解决方案设计矩形区域识别算法自动找到最优合并方案
测试与发布
单元测试
describe(Table Component, () {test(renders basic table, () {render(Table columns{columns} data{data} /)expect(screen.getByText(姓名)).toBeInTheDocument()})test(merge cells correctly, () {const mergeCellMap createMultiRowColumnMergeCellConfig(data, [gender])expect(mergeCellMap.size).toBeGreaterThan(0)})
})发布流程
# 构建
pnpm run build# 发布到npm
pnpm publish --access public总结与展望
通过本次开发我们成功构建了一个功能完整的移动端表格组件主要收获
架构设计合理的类型定义和组件拆分算法优化智能合并算法的设计与实现性能优化虚拟滚动、计算缓存等策略工程化完整的构建、测试、发布流程
未来规划 支持表格编辑功能 增加更多主题样式 优化大数据量性能 支持表格导出功能
参考资料
React官方文档NutUI ReactRollup官方文档 项目地址GitHub - wtechtec/mobile-table
NPM包wtechtec/mobile-table