动端React表格组件:支持合并

前言

在移动端开发中,表格组件是一个常见但复杂的需求。相比PC端,移动端表格面临着屏幕空间有限、交互方式不同、性能要求更高等挑战。本文将详细介绍如何从零开始构建一个功能完整的移动端React表格组件,包含固定列、智能单元格合并、排序等高级功能。

项目背景

在实际项目中,我们经常遇到以下痛点:

  • 现有表格组件在移动端体验不佳
  • 复杂的单元格合并需求难以实现
  • 固定列在不同屏幕尺寸下对齐问题
  • 大数据量下的性能优化

基于这些需求,我们开发了 @wtechtec/mobile-table 组件库。

技术栈选择

  • React 18 - 主框架
  • TypeScript - 类型安全
  • NutUI React - 基础UI组件库
  • Rollup - 构建工具
  • PostCSS - 样式处理

核心功能设计

1. 基础表格结构

首先定义表格的基础类型:

export interface BasicTableProps extends BasicComponent {columns: Array<TableColumnProps>data: Array<any>bordered: booleansummary?: React.ReactNodestriped?: booleannoData?: React.ReactNodesorterIcon?: React.ReactNodeonSort?: (column: TableColumnProps, sortedData: Array<any>) => voidshowHeader?: boolean
}export interface TableColumnProps {key: stringtitle?: stringalign?: stringsorter?: ((a: any, b: any) => number) | boolean | stringrender?: (rowData: any, rowIndex: number) => string | React.ReactNodefixed?: 'left' | 'right'width?: 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 Map<string, { rowSpan: number; colSpan: number; isMainCell: boolean;value: any;mergeType: 'row' | 'column' | 'block';}>()// 创建值到位置的映射const valueToPositions = new Map<any, 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 Set<string>()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-table'const 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-table'const 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

总结与展望

通过本次开发,我们成功构建了一个功能完整的移动端表格组件,主要收获:

  1. 架构设计:合理的类型定义和组件拆分
  2. 算法优化:智能合并算法的设计与实现
  3. 性能优化:虚拟滚动、计算缓存等策略
  4. 工程化:完整的构建、测试、发布流程

未来规划

  • 支持表格编辑功能
  • 增加更多主题样式
  • 优化大数据量性能
  • 支持表格导出功能

参考资料

  • React官方文档
  • NutUI React
  • Rollup官方文档

项目地址:GitHub - @wtechtec/mobile-table

NPM包:@wtechtec/mobile-table

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/news/908975.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

广告系统中后链路数据为什么要使用流批一体技术?流批一体技术是什么?

在大规模广告系统的后链路(离线和实时特征计算、模型训练与上线、效果监控等)中,往往既有对海量历史数据的批量计算需求(离线特征、离线模型训练、报表汇总),又有对在线请求的低延迟实时计算需求(实时特征、在线打分、实时监控/告警)。传统将二者割裂、用 Lambda 架构…

6.10 - 常用 SQL 语句以及知识点

MySQL 技术 SQL 是结构化查询语言&#xff0c;他是关系型数据库的通用语言 SQL 可以分为分为以下三个类别 DDL (data definition languages) 语句 数据定义语言&#xff0c;定义了 不同的数据库、表、索引等数据库对象的定义。常用的的语句关键字包括 **create、drop、alter …

OpenCV CUDA 模块光流计算------稀疏光流算法类SparsePyrLKOpticalFlow

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 OpenCV CUDA 模块中实现的稀疏光流算法类&#xff0c;基于 Lucas-Kanade 方法&#xff0c;并支持图像金字塔结构。适用于特征点跟踪任务&#xf…

免费工具-微软Bing Video Creator

目录 引言 一、揭秘Bing Video Creator 二、轻松上手&#xff1a;三步玩转Bing Video Creator 2.1 获取与访问&#xff1a; 2.2 创作流程&#xff1a; 2.3 提示词撰写技巧——释放AI的想象力&#xff1a; 三、核心特性详解&#xff1a;灵活满足多样化需求 3.1 双重使用模…

MySQL技术内幕1:内容介绍+MySQL编译使用介绍

文章目录 1.整体内容介绍2.下载编译流程2.1 安装编译工具和依赖库2.2 下载编译 3.配置MySQL3.1 数据库初始化3.2 编辑配置文件3.3 启动停止MySQL3.4 登录并修改密码 1.整体内容介绍 MySQL技术系列文章将从MySQL下载编译&#xff0c;使用到MySQL各组件使用原理源码分析&#xf…

MySQL 事务详解

MySQL 事务详解 一、事务是什么&#xff1f;为什么需要事务&#xff1f; 二、事务的四大特性&#xff08;ACID&#xff09;举例说明&#xff1a;转账操作 三、MySQL 中事务的支持四、事务分类&#xff1a;隐式 vs 显式1. 隐式事务&#xff08;自动提交&#xff09;2. 显式事务&…

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录

ASP.NET Core 是一个跨平台的开源框架&#xff0c;用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录&#xff0c;以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…

利用coze工作流制作一个自动生成PPT的智能体

在Coze平台中&#xff0c;通过工作流实现PPT自动化生成是一个高效且灵活的解决方案&#xff0c;尤其适合需要快速产出标准化演示文稿的场景。以下是基于Coze工作流制作PPT的核心逻辑与操作建议&#xff1a; 理论流程 一、核心流程设计 需求输入与解析 用户输入&#xff1a;主…

vue3 按钮级别权限控制

在Vue 3中实现按钮级别的权限控制&#xff0c;可以通过多种方式实现。这里我将介绍几种常见的方法&#xff1a; 方法1&#xff1a;使用Vue 3的Composition API 在Vue 3中&#xff0c;你可以使用Composition API来创建一个可复用的逻辑来处理权限控制。 创建权限控制逻辑 首…

spa首屏加载慢怎样解决

SPA&#xff08;Single Page Application&#xff0c;单页应用&#xff09;首屏加载慢是一个常见问题&#xff0c;主要原因通常是首次加载需要拉取体积较大的 JavaScript 文件、样式表、初始化数据等。以下是一些常见的 优化策略&#xff0c;可以帮助你 提升首屏加载速度&#…

UE5 音效系统

一.音效管理 音乐一般都是WAV,创建一个背景音乐类SoudClass,一个音效类SoundClass。所有的音乐都分为这两个类。再创建一个总音乐类&#xff0c;将上述两个作为它的子类。 接着我们创建一个音乐混合类SoundMix&#xff0c;将上述三个类翻入其中&#xff0c;通过它管理每个音乐…

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…

NTT印地赛车:数字孪生技术重构赛事体验范式,驱动观众参与度革命

引言&#xff1a;数字孪生技术赋能体育赛事&#xff0c;开启沉浸式观赛新纪元 在传统体育赛事观赛模式遭遇体验天花板之际&#xff0c;NTT与印地赛车系列赛&#xff08;NTT INDYCAR SERIES&#xff09;的深度合作&#xff0c;通过数字孪生&#xff08;Digital Twin&#xff09…

解构与重构:PLM 系统如何从管理工具进化为创新操作系统?

在智能汽车、工业物联网等新兴领域的冲击下&#xff0c;传统产品生命周期管理&#xff08;PLM&#xff09;系统正在经历前所未有的范式转换。当某头部车企因 ECU 软件与硬件模具版本失配导致 10 万辆智能电车召回&#xff0c;损失高达 6 亿美元时&#xff0c;这场危机不仅暴露了…

【Ubuntu 16.04 (Xenial)​​】安装docker及容器详细教程

Ubuntu 16.04 安装docker详细教程 一、docker安装1.1 前期准备1.2 使用 Docker 官方安装脚本安装&#xff08;推荐&#xff09; 查看ubuntu版本&#xff1a;lsb_release -a 这里我的系统是 ​​Ubuntu 16.04 (Xenial)​​&#xff0c;在 ​​Ubuntu 16.04 (Xenial)​​ 上安装…

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…

MySQL:InnoDB架构(内存架构篇)

目录 0.前置知识 0.1二级索引的概念 二级索引查询原理 1.整体架构 1.1为什么innoDB的架构会分为两个部分? 2.内存架构 2.1BufferPool 2.2ChangeBuffer 唯一性检查不是实时性会出现的问题? ChangeBuffer的优势 2.3Adaptive Hash Index 2.4LogBuffer 0.前置知识 0.…

鹰盾加密器“一机一码”技术全维度剖析:从底层实现到生态防护体系

“一机一码”加密技术的深度解析与实现路径 引言 在数字内容版权保护和软件授权管理领域&#xff0c;“一机一码”技术作为一种重要的安全防护手段&#xff0c;能够有效防止授权码滥用和非法传播。它通过建立设备与授权码的唯一对应关系&#xff0c;确保每份授权仅在特定设备…

Android 中使用 OkHttp 创建多个 Client

在 Android 开发中&#xff0c;有时我们需要创建多个 OkHttpClient 实例来满足不同的网络请求需求。以下是创建和管理多个 OkHttpClient 的方法&#xff1a; 基本创建方式 // 创建默认的 OkHttpClient val defaultClient OkHttpClient()// 创建带有自定义配置的 Client val …

C++中的跳转语句

C中的跳转语句包括break、continue和goto&#xff0c;它们用于改变程序的正常执行流程。下面分别介绍它们的作用、使用场景和注意事项&#xff1a; 1. break 作用&#xff1a; • 立即终止当前所在的循环&#xff08;for、while、do while&#xff09;或switch语句&#xff…