[React]如何提高大数据量场景下的Table性能?

[React]如何提高大数据量场景下的Table性能?

两个方向:虚拟列表,发布订阅

虚拟列表

虚拟列表实际上只对可视区域的数据项进行渲染

  • 可视区域(visibleHeight): 根据屏幕可视区域动态计算或自定义固定高度
  • 数据渲染项(visibleCount):可视区域除以行高并向下取整
  • startIndex: 初始为0,听过滚动条偏移计算
  • endIndex: startIndex + visibleCount; 数据结束位置索引,可额外预加载几条数据

实现思路

监听逻辑实现

  useEffect(() => {const onScrollChange = (e: React.WheelEvent) => {const top = (e.target as HTMLElement).scrollTopconst index = Math.floor(top / rowHeight)setScrollTop(top)setStartIndex(index ? index + 1 : 0)}virtualizedRef.current.addEventListener('scroll', onScrollChange)return () => {if (virtualizedRef.current) {virtualizedRef.current.removeEventListener('scroll', onScrollChange)}}}, [])

HTML结构如下:

  • virtualized_placeholder: 容器内占位,高度为列表总高度,撑满父容器,用于可视区域形成滚动条
<div ref={virtualizedRef} style={{ height: visibleHeight }}><table style={{ transform: `translate3d(0px, ${scrollTop}px, 0)` }}><thead>{...}</thead><tbody>{...}</tbody></table><div className="virtualized_placeholder" style={{ height: placeHeight }} /></div>

主要逻辑

  • 设置容器占位高度,计算可视区域数据项
  • 监听容器滚动事件,计算偏移距离,startIndex,组件卸载移除滚动事件
  • startIndex作为deps依赖项,当发生改变更新展示数据
useEffect(() => {const placeH = ((dataSource.length) * rowHeight) + rowHeightsetPlaceHeight(placeH)setVisibleCount(Math.floor(visibleHeight / rowHeight) + 2)}, [dataSource, rowHeight])useEffect(() => {const onScrollChange = (e: React.WheelEvent) => {const top = (e.target as HTMLElement).scrollTopconst index = Math.floor(top / rowHeight)setScrollTop(top)setStartIndex(index ? index + 1 : 0)}virtualizedRef.current.addEventListener('scroll', onScrollChange)return () => {if (virtualizedRef.current) {virtualizedRef.current.removeEventListener('scroll', onScrollChange)}}}, [])useEffect(() => {const data = dataSource.slice(startIndex, startIndex + visibleCount)setShowData(data)}, [startIndex, visibleCount, dataSource])

完整代码

/*** dataSource 数据数组 object[]* columns 表格列 string[]* rowKey 表格行key的取值 number | string* rowHeight tr固定高度 number* visibleHeight 可视区域高度 number* hasOrder 是否含有序号 boolean* orderTitle 序号标题 string*/
import React, { FC, useEffect, useState, useRef, memo } from 'react'
import { Empty } from 'antd';
import './index.less'interface DataProps {[key:string]: any
}
interface VirtualProps {dataSource: DataProps[]columns: string[]rowKey?: number | stringhasOrder?: booleanorderTitle?: stringrowHeight?: numbervisibleHeight?: number
}const Index: FC<VirtualProps> = (props) => {const {dataSource = [],columns = [],rowKey,hasOrder = false,orderTitle = '序号',rowHeight = 40,visibleHeight = 800,} = propsconst [startIndex, setStartIndex] = useState(0)const [placeHeight, setPlaceHeight] = useState(0)const [scrollTop, setScrollTop] = useState(0)const [visibleCount, setVisibleCount] = useState(0)const [showData, setShowData] = useState<DataProps[]>([])const virtualizedRef = useRef<any>(null)useEffect(() => {const placeH = ((dataSource.length) * rowHeight) + rowHeightsetPlaceHeight(placeH)setVisibleCount(Math.floor(visibleHeight / rowHeight) + 2)}, [dataSource, rowHeight])useEffect(() => {const onScrollChange = (e: React.WheelEvent) => {const top = (e.target as HTMLElement).scrollTopconst index = Math.floor(top / rowHeight)setScrollTop(top)setStartIndex(index ? index + 1 : 0)}virtualizedRef.current.addEventListener('scroll', onScrollChange)return () => {if (virtualizedRef.current) {virtualizedRef.current.removeEventListener('scroll', onScrollChange)}}}, [])useEffect(() => {const data = dataSource.slice(startIndex, startIndex + visibleCount)setShowData(data)}, [startIndex, visibleCount, dataSource])return (<div className="galois_virtualized_container" ref={virtualizedRef} style={{ height: visibleHeight }}><tablestyle={{ transform: `translate3d(0px, ${scrollTop}px, 0)` }}className="galois_virtualized_table"><thead><tr>{hasOrder && <th key="galois_index">{orderTitle}</th>}{columns.map(values => <th key={values}>{values}</th>)}</tr></thead><tbody>{showData.map((item, index) => (<tr key={rowKey ? item[rowKey] : index}>{hasOrder && <td>{startIndex + index + 1}</td>}{columns.map((values, ind) => <td key={ind}>{item[values]}</td>)}</tr>))}</tbody></table>{showData.length === 0 &&  <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}<div className="galois_virtualized_placeholder" style={{ height: placeHeight }} /></div>)
}export default memo(Index)

利用发布订阅模式优化批量编辑的场景

正常情况下来说,把整个表格的数据都挂载到一个state中是最简单的,但是这么做的话,每次单元格在编辑onChange的时候就会setState,从而更新整个table,在数据稍大的场景下,编辑的性能会非常低,用户每输入一个字都要rerender。

发布订阅可以帮我们去掉这一部分冗余的rerender,从而做到每个cell的onChange都是单独的。

预期下的单元格状态维护

每个cell都进行单独的状态管理,每个cell内部都是用

const [data, setData] = useState(defaultValue)return <Input value={data} onChange={(e)=>setData(e.target.value)}/>

来维护内容,这样的话即使onChange,也只是rerender这个单独的cell,不会影响到整个table。

发布订阅实现

export interface IRef {id: string[key: string]: any
}// 发布订阅模式
export class RefCollection {// 订阅者集合private refMap: Map<string, IRef>constructor() {this.refMap = new Map<string, IRef>()}// 添加订阅者public addRef(ref: IRef) {if (!this.refMap.has(ref.id)) {this.refMap.set(ref.id, ref)}}// 移除订阅者public removeRef(ref: IRef) {this.refMap.delete(ref.id)}// -----------------------下面是广播事件----------------------------------// 触发所有deps的submit方法public submit() {return Array.from(this.refMap.values()).map((oneRef => {return oneRef.submit?.()}))}// 触发所有deps的validate方法public validate() {return Array.from(this.refMap.values()).map((oneRef => {return oneRef.validate?.()}))}// ...其它
}

收集每个单元格的依赖

业务组件内:

// 注册一个收集器
const refCollection = useRef(new RefCollection())const columns = [{dataIndex: 'title',render(){return <Cell refCollection={refCollection}/>}}
]

单元格内部逻辑:

// 每一个Cell内const Cell = (props)=>{const { refCollection } = props// 每一个Cell内部自己实现接口,逻辑独立,只需关注自己即可const ref = useRef<any>({// 当前单元格的唯一标识id: uid()// 这里随便加什么属性,可以加一些type来区别不同的Cell// 比如说有些是Select的控件,有些是Input的控件// 在submit的时候就可以根据type来过滤收集type: "inputRender",// 一般来说,可能要给定一个行号,因为我们提交数据的时候都是按行提交的// 有了行ID之后我们可以在submit的时候聚合数据,转换成需要提交的格式tableRowId: row?.tableRowId,validate: useMemoizedFn(() => {// 在这里实现自己的validate方法 // refCollection执行validate的时候会遍历每一个订阅者的validate方法// return boolean}),submit: useMemoizedFn(() => {// 在这里实现自己的submit方法// refCollection执行validate的时候会遍历每一个订阅者的submit方法// return {}}),})// 在这里收集依赖useEffect(() => {if (!refCollection) returnrefCollection?.add(ref.current)return () => {refCollection?.remove(ref.current)}}, [])return <div></div>
}

提交阶段


const refCollection = useRef(new RefCollection())const onSubmit = ()=>{await refCollection.current.validateAll()const data = refCollection.current.submit()// 提交逻辑 data
}

为什么不用FormItem?

  • FormItem包含了其它很多逻辑,但是未必都需要用得上
  • 如果一个单元格就要多渲染一层FormItem,整体下来就会非常地损耗性能
  • FormItem如果不渲染出来,那么就无法做逻辑,而如果通过统一的状态管理,可以实现字段不渲染出来就能完成值的读取和修改,实现虚拟字段的效果(这时候可以搭配分页、虚拟列表提高性能),同时也能正常地兼顾一些联动操作(比如说表格数字汇总)

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

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

相关文章

python_合并同一个文件夹下的excel文件

python_合并同一个文件夹下的excel文件 import os import glob import pandas as pddef merge_excel_sheets(input_folder, output_file):# 创建一个空的 DataFrame 用于存储所有数据combined_data pd.DataFrame()# 获取指定文件夹内所有的 Excel 文件excel_files glob.glob…

el-select下拉数据量太大,改成滚动加载数据

问题描述&#xff1a;当接口返回下拉数据量特别大的时候&#xff0c; 页面会卡顿&#xff0c; 下面采用下拉加载指定数据的方式来优化。 <template><el-selectv-model"value"filterableplaceholder"Select"v-focus"loadData(loadNumber)&qu…

(面试必看!)一些和多线程相关的面试考点

文章导读 引言考点1. CAS 指令&#xff08;重点&#xff09;一、什么是CAS二、CAS 的优点三、CAS 的缺点四、ABA问题五、相关面试题 考点2. 信号量&#xff08;semaphore&#xff09;一、基本概念二、信号量的主要操作三、信号量的应用四、相关面试题 考点3、CountDownLatch 类…

DHCP笔记

DHCP---动态主机配置协议 作用&#xff1a;为终端动态提供IP地址&#xff0c;子网掩码&#xff0c;网关&#xff0c;DNS网址等信息 具体流程 报文抓包 在DHCP服务器分配iP地址之间会进行广播发送arp报文&#xff0c;接收IP地址的设备也会发送&#xff0c;防止其他设备已经使用…

网络编程 - 粘包与拆包第一弹 - 深入理解TCP粘包与拆包问题

作者&#xff1a;逍遥Sean 简介&#xff1a;一个主修Java的Web网站\游戏服务器后端开发者 主页&#xff1a;https://blog.csdn.net/Ureliable 觉得博主文章不错的话&#xff0c;可以三连支持一下~ 如有疑问和建议&#xff0c;请私信或评论留言&#xff01; 前言 在网络编程中&a…

Unity3D 二进制序列化器详解

前言 在Unity3D开发中&#xff0c;二进制序列化是一种重要的数据持久化和网络传输技术。通过二进制序列化&#xff0c;游戏对象或数据结构可以被转换成二进制格式&#xff0c;进而高效地存储于文件中或通过网络传输。本文将详细介绍Unity3D中的二进制序列化技术&#xff0c;包…

如何利用 NLP 技术提高机器翻译中对文化特定词汇和习语的理解与翻译准确性?

要利用 NLP 技术提高机器翻译中对文化特定词汇和习语的理解与翻译准确性&#xff0c;可以采用以下方法&#xff1a; 数据收集与预处理&#xff1a;收集与文化特定词汇和习语相关的大量平行语料&#xff0c;确保数据集中包含丰富的文化特定内容。进行数据预处理&#xff0c;包括…

【手撕数据结构】栈和队列高频面试题

目录 括号匹配问题用队列实现栈用栈实现队列 括号匹配问题 给定一个只包括 ‘(’&#xff0c;‘)’&#xff0c;‘{’&#xff0c;‘}’&#xff0c;‘[’&#xff0c;‘]’ 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 1.左括号必须用相同类…

卓码软件测评:软件功能测试和非功能测试详情介绍

随着信息技术的不断发展&#xff0c;软件在我们日常生活与工作中扮演着越来越重要的角色。然而&#xff0c;软件质量的好坏直接关系到使用者的体验和企业的声誉。在软件开发过程中&#xff0c;功能测试和非功能测试作为保证软件质量的重要手段&#xff0c;受到了越来越多的关注…

【过题记录】 7.28 (树上dp,背包,换根,基环树)

[ZJOI2007] 时态同步 分析&#xff1a; 不难发现&#xff0c;中断点就是叶子节点&#xff0c; 首先&#xff0c;所有叶子节点的高度肯定就等于最深的那个叶子节点的深度。 且不可能去调整最深的叶子结点的深度了。 这样经过一遍dfs之后我们可以计算出每个叶子需要增加的高度。…

古文:文天祥《正气歌》

原文 正气歌 【作者】文天祥 【朝代】宋 余囚北庭&#xff0c;坐一土室。室广八尺&#xff0c;深可四寻。单扉低小&#xff0c;白间短窄&#xff0c;污下而幽暗。当此夏日&#xff0c;诸气萃然&#xff1a;雨潦四集&#xff0c;浮动床几&#xff0c;时则为水气&#xff1b;涂泥…

内容营销专家刘鑫炜:极狐车自燃风波自救,堪称品牌危机公关范本

近日&#xff0c;极狐电车自燃事件在社交媒体上迅速发酵&#xff0c;尤其是厂家在事故现场的第一反应——先抠车标、覆盖黑布的行为&#xff0c;更是引发了公众的广泛质疑与愤慨。这一突发事件不仅考验着极狐汽车的产品安全性能&#xff0c;更对其品牌危机公关能力提出了严峻挑…

YAML 语法规范

文章目录 YAML 语法规范一、简介二、基本语法三、高级语法四、示例解析五、注意事项YAML 语法规范 一、简介 YAML(YAML Ain’t Markup Language)是一种专门用来写配置文件的语言,具有简洁、易读、易解析等特点。YAML的设计理念是为人类和机器之间的沟通提供一种更加直观、…

Chiplet SPI User Guide 详细解读

目录 一. 基本介绍 1.1.整体结构 1.2. 结构细节与功能描述 二. 输入输出接口 2.1. IO Ports for SPI Leader 2.2. IO Ports for SPI Follower 2.3. SPI Mode Configuration 2.4. Leader IP和Follower IP功能图 三. SPI Programming 3.1. Leader Register Descripti…

基于FPGA的数字信号处理(19)--行波进位加法器

1、10进制加法是如何实现的&#xff1f; 10进制加法是大家在小学就学过的内容&#xff0c;不过在这里我还是帮大家回忆一下。考虑2个2位数的10进制加法&#xff0c;例如&#xff1a;15 28 43&#xff0c;它的运算过程如下&#xff1a; 个位两数相加&#xff0c;结果为5 8 1…

【elementui】记录如何重命名elementui组件名称

在main.js中&#xff0c;就是引入elementui的文件中 import ElementUI from element-ui import { Tree } from element-uiVue.use(ElementUI) Vue.component(el-tree-rename, Tree)

c++——vector容器详解

vector vector概述主要特点和优势&#xff1a;使用示例&#xff1a;成员类型 vector函数构造函数迭代器函数size和capacity函数操作函数 vector-bool特点和用法&#xff1a;示例用法&#xff1a; vector概述 C 中的 std::vector 是一个动态数组&#xff0c;是标准模板库&#…

苹果 iCloud 钥匙串是什么?如何查看及对其进行设置?

在当今的数字世界中安全性和便利性是人们关注的两大重点。无论是社交媒体账户、还是网购平台等&#xff0c;几乎每个在线服务都需要登录账户。如何安全地管理和存储这些账户密码成为了用户们的一大挑战。 iCloud 钥匙串 我们先来看一看什么是 iCloud 钥匙串&#xff0c;iClou…

Photoshop钢笔工具

一、钢笔工具概述 Photoshop中的钢笔工具&#xff08;快捷键为P&#xff09;是矢量绘图工具&#xff0c;主要用于精确绘制直线、曲线以及开放或闭合的路径。钢笔工具通过锚点和方向线来定义路径的形状&#xff0c;可以绘制出平滑的曲线和直线段&#xff0c;非常适合用于抠图、…

Redis:事务

1. 简介 可以一次性执行多个命令&#xff0c;本质是一组命令的集合。一个事务中的所有命令都会序列化&#xff0c;按顺序的串化执行&#xff0c;不允许被其他其他命令插入&#xff0c;不许加塞 即将要执行的命令放入队列中&#xff0c;此时该队列的所有命令就是一个事务&#x…