当前位置: 首页 > news >正文

[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如果不渲染出来,那么就无法做逻辑,而如果通过统一的状态管理,可以实现字段不渲染出来就能完成值的读取和修改,实现虚拟字段的效果(这时候可以搭配分页、虚拟列表提高性能),同时也能正常地兼顾一些联动操作(比如说表格数字汇总)

相关文章:

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

[React]如何提高大数据量场景下的Table性能&#xff1f; 两个方向&#xff1a;虚拟列表&#xff0c;发布订阅 虚拟列表 虚拟列表实际上只对可视区域的数据项进行渲染 可视区域&#xff08;visibleHeight&#xff09;: 根据屏幕可视区域动态计算或自定义固定高度数据渲染项&…...

基于Vision Transformer的mini_ImageNet图片分类实战

【图书推荐】《PyTorch深度学习与计算机视觉实践》-CSDN博客 PyTorch计算机视觉之Vision Transformer 整体结构-CSDN博客 mini_ImageNet数据集简介与下载 mini_ImageNet数据集节选自ImageNet数据集。ImageNet是一个非常有名的大型视觉数据集&#xff0c;它的建立旨在促进视觉…...

JS中map()使用记录

优点和缺点 总的来说&#xff0c;map() 方法是一个强大的工具&#xff0c;适合于需要将数组中的每个元素转换为新形式的情况。然而&#xff0c;对于性能敏感的应用或需要更复杂控制逻辑的场景&#xff0c;可能需要考虑其他方法。 优点&#xff1a; 函数式编程风格&#xff1a…...

JavaWeb学习——请求响应、分层解耦

目录 一、请求响应学习 1、请求 简单参数 实体参数 数组集合参数 日期参数 Json参数 路径参数 总结 2、响应 ResponseBody&统一响应结果 二、分层解耦 1、三层架构 三层架构含义 架构划分 2、分层解耦 引入概念 容器认识 3、IOC&DI入门 4、IOC详解 …...

Vue中!.和?.是什么意思

在Vue&#xff08;或更广泛地说&#xff0c;在JavaScript和TypeScript中&#xff09;&#xff0c;!. 和 ?. 是两个与可选链&#xff08;Optional Chaining&#xff09;和断言非空&#xff08;Non-null Assertion&#xff09;相关的操作符&#xff0c;它们分别用于处理可能为nu…...

秋招突击——7/22——复习{堆——前K个高频元素}——新作{回溯——单次搜索、分割回文串。链表——环形链表II,合并两个有序链表}

文章目录 引言复习堆堆——前K个高频元素个人实现复习实现二参考实现 新作单词搜索个人实现参考实现 分割回文串个人实现参考实现 环形链表II个人实现参考实现 两个有序链表个人实现 总结 引言 又是充满挑战性的一天&#xff0c;继续完成我们的任务吧&#xff01;继续往下刷&a…...

android13禁用某个usb设备

总纲 android13 rom 开发总纲说明 目录 1.前言 2.触摸设备查看 3.功能修改 3.1 禁用usb触摸 3.2 禁用usb键盘 3.3 禁用usb遥感 4.查看生效与否 5.彩蛋 1.前言 用户想要禁止使用某些usb设备,需要系统不能使用相关的usb设备,例如usb触摸屏,usb键盘,usb遥感等等usb…...

tmux相关命令

tmux相关命令 1、tmux介绍2、会话&#xff08;session&#xff09;、窗口&#xff08;windows&#xff09;、窗格&#xff08;pane&#xff09;3、会话相关命令4、窗口相关命令5、窗格相关命令6、内容查看7、tmux配置文件 1、tmux介绍 略 2、会话&#xff08;session&#xf…...

初创小程序公司怎么选服务器合作商

初创小程序公司怎么选服务器合作商&#xff1f;在移动互联网的浪潮中&#xff0c;小程序以其轻量、便捷、即用即走的特点&#xff0c;成为了众多初创企业快速触达用户、展现创意与服务的理想平台。然而&#xff0c;对于初创小程序公司而言&#xff0c;如何在纷繁复杂的服务器市…...

基于微信小程序+SpringBoot+Vue的自习室选座与门禁系统(带1w+文档)

基于微信小程序SpringBootVue的自习室选座与门禁系统(带1w文档) 基于微信小程序SpringBootVue的自习室选座与门禁系统(带1w文档) 本课题研究的研学自习室选座与门禁系统让用户在小程序端查看座位&#xff0c;预定座位&#xff0c;支付座位价格&#xff0c;该系统让用户预定座位…...

【Linux】进程IO|重定向|缓冲区|dup2|dup|用户级缓冲区|模拟缓冲区

目录 前言 重定向 实验一 为什么log.txt文件的文件描述符是1 为什么向stdout打印的信息也出现在文件中 实验二 用户级缓冲区 为什么要有用户级缓冲区 系统调用 dup 为什么close(fd1)之后还能向log.txt写入数据&#xff1f; dup2 缓冲区 观察现象 测试1 测试2 测…...

bug bug bug

importError: DLL load failed while importing _multiarray_umath: 找不到指定的模块。 Traceback (most recent call last): File "D:\yolov8_about\ultralytics-main3\trainCPU.py", line 4, in <module> from ultralytics import YOLO File "…...

医疗器械上市欧美,需要什么样的网络安全相关申报文件?

医疗器械在欧美上市时&#xff0c;需要提交的网络安全相关申报文件主要包括以下几个方面&#xff0c;这些要求基于欧美地区的法律法规和监管机构的指导文件。 一、美国FDA要求 1. 网络安全管理计划 内容&#xff1a;制造商需要提交一份网络安全管理计划&#xff0c;该计划应包含…...

【UbuntuDebian安装Nginx】在线安装Nginx

云计算&#xff1a;腾讯云轻量服务器 操作系统&#xff1a;Ubuntu-v22 1.更新系统软件包列表 打开终端并运行以下命令来确保你的系统软件包列表是最新的&#xff1a; sudo apt update2.安装 Nginx 使用以下命令安装 Nginx&#xff1a; sudo apt install nginx3.启动 Nginx…...

Jacoco 单元测试配置

前言 编写单元测试是开发健壮程序的有效途径&#xff0c;单元测试写的好不好可以从多个指标考量&#xff0c;其中一个就是单元测试的覆盖率。单元测试覆盖率可以看到我们的单元测试覆盖了多少代码行、类、分支等。查看单元测试覆盖率可以使用一些工具帮助我们计算&#xff0c;…...

App Instance 架构示例

前言 在Unity程序设计过程中&#xff0c;我们处理的第一个对象是Application Instance。 它的主要职责是启动流程管理、卸载流程管理&#xff0c;次要职责是管理在内部的子系统生命周期。其他职责&#xff0c;提供或桥接应用程序的配置信息、及其他第三方接口。 它通常以单例的…...

【论文速读】| MoRSE:利用检索增强生成技术填补网络安全专业知识的空白

本次分享论文&#xff1a;MoRSE: Bridging the Gap in Cybersecurity Expertise with Retrieval Augmented Generation 基本信息 原文作者&#xff1a;Marco Simoni, Andrea Saracino, Vinod Puthuvath, Maurco Conti 作者单位&#xff1a;意大利比萨国家研究委员会信息学与…...

pip install albumentations安装下载超级细水管

albumentations 是一个用于图像增强的 Python 库&#xff0c;它提供了丰富的图像变换功能&#xff0c;可以用于数据增强&#xff0c;从而提高深度学习模型的泛化能力。 直接安装命令&#xff1a; pip install albumentations但是如果半夜遇到这种19kB/s的下载速度 为头发着想&…...

驱动开发系列07 - 驱动程序如何分配内存

一:概述 Linux 内核提供了丰富的内存分配函数、在本文中,我们将介绍在设备驱动程序中分配和使用内存的方法,以及如何优化系统的内存资源。由于内核为驱动程序提供了统一的内存管理接口。所以我们不会去讨论不同架构是如何管理内存的,文本不涉及分段、分页等问题,此外在本文…...

【Jackson】注解及其使用

Jackson库提供了多种注解&#xff08;annotations&#xff09;&#xff0c;可以用来控制JSON序列化和反序列化的行为。这些注解允许你灵活地映射Java对象与JSON数据之间的关系。下面将详细介绍一些常用的Jackson注解及其用法。 1. JsonProperty 作用: 用于指定JSON属性与Java…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

c++ 面试题(1)-----深度优先搜索(DFS)实现

操作系统&#xff1a;ubuntu22.04 IDE:Visual Studio Code 编程语言&#xff1a;C11 题目描述 地上有一个 m 行 n 列的方格&#xff0c;从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子&#xff0c;但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

自然语言处理——循环神经网络

自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元&#xff08;GRU&#xff09;长短期记忆神经网络&#xff08;LSTM&#xff09…...

Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?

Redis 的发布订阅&#xff08;Pub/Sub&#xff09;模式与专业的 MQ&#xff08;Message Queue&#xff09;如 Kafka、RabbitMQ 进行比较&#xff0c;核心的权衡点在于&#xff1a;简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...

论文笔记——相干体技术在裂缝预测中的应用研究

目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术&#xff1a;基于互相关的相干体技术&#xff08;Correlation&#xff09;第二代相干体技术&#xff1a;基于相似的相干体技术&#xff08;Semblance&#xff09;基于多道相似的相干体…...

智能AI电话机器人系统的识别能力现状与发展水平

一、引言 随着人工智能技术的飞速发展&#xff0c;AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术&#xff0c;在客户服务、营销推广、信息查询等领域发挥着越来越重要…...

C++ 设计模式 《小明的奶茶加料风波》

&#x1f468;‍&#x1f393; 模式名称&#xff1a;装饰器模式&#xff08;Decorator Pattern&#xff09; &#x1f466; 小明最近上线了校园奶茶配送功能&#xff0c;业务火爆&#xff0c;大家都在加料&#xff1a; 有的同学要加波霸 &#x1f7e4;&#xff0c;有的要加椰果…...

day36-多路IO复用

一、基本概念 &#xff08;服务器多客户端模型&#xff09; 定义&#xff1a;单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用&#xff1a;应用程序通常需要处理来自多条事件流中的事件&#xff0c;比如我现在用的电脑&#xff0c;需要同时处理键盘鼠标…...

前端调试HTTP状态码

1xx&#xff08;信息类状态码&#xff09; 这类状态码表示临时响应&#xff0c;需要客户端继续处理请求。 100 Continue 服务器已收到请求的初始部分&#xff0c;客户端应继续发送剩余部分。 2xx&#xff08;成功类状态码&#xff09; 表示请求已成功被服务器接收、理解并处…...