如何解决大数据下滚动页面卡顿问题

news2024/11/16 19:30:55

原文合集地址如下,有需要的朋友可以关注

本文地址

合集地址

前言

之前遇到不分页直接获取到全部数据,前端滚动查看数据,页面就听卡顿的,当然这和电脑浏览器性能啥的还是有点关系。但根源还是一次性渲染数据过多导致的,因此就想到解决这个问题,最常见就是虚拟滚动,实现只渲染当前可见的部分,这样浏览器一次性渲染的数据少了。
本文介绍虚拟列表和虚拟Table的实现,基于React + ts技术栈。

虚拟列表

虚拟列表通过仅渲染当前可见区域的列表项来解决这个问题。它利用浏览器的滚动事件,根据用户可见区域的大小和滚动位置动态地计算应该渲染哪些列表项。这样,即使数据量很大,也只有当前可见的列表项会被渲染,大大减少了DOM元素的数量,提高了页面性能和响应性。
结合下图想象一下
在这里插入图片描述

实现虚拟列表的方法主要涉及以下步骤:

  1. 计算可见区域:根据容器的尺寸(假如500px)和每一项的高度(50px),计算出可见的列表项数量。然后可视的数据就是10个。

  2. 监听滚动事件:在容器上添加滚动事件监听,以便实时获取滚动位置。为了容器可滚动,需要在容器内添加空的带有高度的元素,将父元素撑开,然后可滚动。获取scrollTop的高度,就能计算出当前显示第一项的下标(scrollTop / itemHeight),动态更新数据。

基于上面的思路,封装一个滚动列表组件。

import _ from "lodash";
import React, { useEffect, useState } from "react";
import { listData } from "./data";

type ListType = {
  itemHeight?: number; // 每一项的高度
  visibleHeight?: number; // 可见高度
  total?: number; // 数据总数
  dataSource?: any[]; // 全部数据
};

// 为了看效果我模拟的数据
const myList = Array.from(Array(1000), (item, index) => ({name: `名字${item}`, id: index}));

const List = (props: ListType) => {
  const {
    itemHeight = 54,
    visibleHeight = 540,
    total = 130,
    dataSource = myList,
  } = props;
  const [showData, setShowData] = useState<any>([]);
  const [offset, setOffset] = useState<any>({ top: 0, bottom: 0 });
  const visibleCount = Math.ceil(visibleHeight / itemHeight);

  useEffect(() => {
    const list = _.slice(dataSource, 0, visibleCount);
    const bottom = (total - visibleCount) * itemHeight;
    setOffset({ top: 0, bottom });
    setShowData(list);
  }, [dataSource]);

  const onScroll = (event: React.UIEvent<HTMLDivElement>) => {
    const target = event.currentTarget;
    const startIdx = Math.floor(target.scrollTop / itemHeight);
    const endIdx = startIdx + visibleCount;
    setShowData(dataSource.slice(startIdx, endIdx));
    const top = startIdx * itemHeight;
    const bottom = (total - endIdx) * itemHeight;

    setOffset({ top, bottom });
  };

  return (
    <div
      className="virtual"
      style={{
        height: visibleHeight,
        width: "100%",
        overflow: "auto",
        border: "1px solid #d9d9d9",
      }}
      onScroll={onScroll} // 在父元素上添加滚动事件监听
    >
      {/* 可视数据 为了滚动数据一直在可视区。加上顶部偏移 */}
      <div style={{ height: visibleHeight, marginTop: offset.top }}>
        {_.map(showData, (item, index: any) => {
          return (
            <div
              style={{
                display: "flex",
                alignItems: "center",
                height: itemHeight,
                borderBottom: "1px solid #d9d9d9",
              }}
              key={index}
            >
              {item.name}
            </div>
          );
        })}
      </div>
      {/* 底部占位 */}
      <div style={{ height: offset.bottom }} />
    </div>
  );
};

export default List;

虚拟Table

虚拟表格和虚拟列表的思路差不多,是虚拟列表的一种特殊形式,通常用于处理大型的表格数据。类似于虚拟列表,虚拟表格也只渲染当前可见区域的表格单元格,以优化性能并减少内存占用。
在ant design4+的版本,也是给出了虚拟列表的实现方式的,基于‘react-window’,大家也可以研究研究。我这里就是根据ant 提供的api components重写渲染的数据;获取到可视区起点和终点下标,然后只展示当前可视的数据。
思路和上面的列表基本一样,直接上代码

import React, { useEffect, useRef, useState } from "react";
import { Table } from "antd";
import _ from "lodash";
type TableType = {
  itemHeight?: number; // 每一项的高度
  visibleHeight?: number; // 可见高度
  total?: number; // 数据总数
  dataSource?: any[]; // 全部数据
};
// 为了看效果我模拟的数据
const myList = Array.from(Array(1000), (item, index) => ({name: `名字${item}`, id: index}));

const VirtualTable = (props: TableType) => {
  const {
    itemHeight = 54,
    visibleHeight = 540,
    total = 130,
    dataSource = myList,
  } = props;
  const [point, setPoint] = useState<any>([0, 20]);
  const [offset, setOffset] = useState<any>({top:0, bottom: 0 });
  const tabRef = useRef<any>();
  const containRef = useRef<any>();
  const visibleCount = Math.ceil(visibleHeight / itemHeight);
  useEffect(() => {
    const bottom = (total - visibleCount) * itemHeight;
    setOffset({ bottom });
    setPoint([0, visibleCount]);
    const scrollDom = tabRef?.current?.querySelector(".ant-table-body");
    console.log("aaa",scrollDom);
    
    if (scrollDom) {
      containRef.current = scrollDom;
      containRef.current.addEventListener("scroll", onScroll);

      return () => {
        containRef.current.removeEventListener("scroll", onScroll);
      };
    }
  }, [myList]);

  const onScroll = (e: any) => {
    const startIdx = Math.floor(e?.target?.scrollTop / itemHeight);
    const endIdx = startIdx + visibleCount;
    const bottom = (total - endIdx) * itemHeight;
    const top = startIdx * itemHeight;
    setOffset({top,bottom});
    setPoint([startIdx, endIdx]);
  };

  const columns = [
    { title: "ID", dataIndex: "id", width: 150 },
    { title: "名字", dataIndex: "name", width: 150 },
  ];

  return (
    <Table
      ref={tabRef}
      className="virtual-table"
      pagination={false}
      columns={columns}
      dataSource={dataSource}
      scroll={{ y: visibleHeight }}
      components={{
        body: {
          wrapper: ({ className, children }: any) => {
            return (
              <tbody className={className}>
                {children?.[0]}
                <tr style={{height: offset.top}}/>
                {_.slice(children?.[1], point?.[0], point?.[1])}
                <tr style={{height: offset.bottom}}></tr>
              </tbody>
            );
          },
        },
      }}
    />
  );
};
export default VirtualTable;

在上面的代码里,用到Ant Design的Table组件中的components.body.wrapper定制表格内容区域的包装器组件。它的作用是对表格的内容进行包装,并且可以自定义一些显示逻辑。components.body.wrapper函数接收一个对象参数,其中包含以下参数:

  1. className: 传递给tbody标签的类名。它是一个字符串,包含了tbody标签的类名,可以用于自定义样式。

  2. children: 表格的内容区域的子元素,即表格的数据行和列。

在给定的代码中,components.body.wrapper函数接收了一个参数对象,该对象包含classNamechildren属性。在函数内部,它会将children分割成三部分:

  1. children?.[0]:这是表格的标题行,即表头部分,对应于<thead>标签。

  2. {_.slice(children?.[1], point?.[0], point?.[1])}:这是表格的数据行,根据point的取值进行了切片,只渲染point范围内的数据行,对应于<tr>标签。

  3. <tr style={{height: offset.bottom}}></tr>:这是底部占位行,用于确保在滚动时能正确显示表格的底部内容,对应于<tr>标签,并通过style设置高度为offset.bottom

其中,pointoffset是通过其他逻辑计算得到的,可能是在组件的其他部分定义或使用的变量。

通过自定义components.body.wrapper函数,您可以对表格内容进行更加灵活的渲染和定制。在这种情况下,它主要用于实现虚拟表格的功能,只渲染可见区域的数据行,从而优化大型表格的性能。

总结

本文只是实现了在固定每项列表高度的情况下的虚拟列表,现实很多情况是不定高的。这个比定高的复杂,不过原理也是一样的,多了一步需要计算渲染后的实际高度的步骤。我也只是在项目中遇到了,写下来记录方便后续查看。

如有问题欢迎留言讨论,如有更好的实现方式,可以交流学习哦

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

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

相关文章

【Git】初始化仓库配置与本地仓库提交流程

目录 一、仓库配置邮箱与用户名 二、本地仓库提交流程 一、仓库配置邮箱与用户名 【Git】Linux服务器Centos环境下安装Git与创建本地仓库_centos git仓库搭建_1373i的博客-CSDN博客https://blog.csdn.net/qq_61903414/article/details/131260033?spm1001.2014.3001.5501 在…

JavaScript基础篇(61-70题)

此文章&#xff0c;来源于印客学院的资料【第一部分&#xff1a;基础篇(105题)】&#xff0c;也有一些从网上查找的补充。 这里只是分享&#xff0c;便于学习。 诸君可以根据自己实际情况&#xff0c;自行衡量&#xff0c;看看哪里需要加强。 概述如下&#xff1a; 希望获取…

2023年深圳杯A题赛题详细解析1.1版本

A题 影响城市居民身体健康的因素分析 附件A1是某市卫生健康研究部门对部分居民所做的“慢性非传染性疾病及其相关影响因素流行病学”调查问卷表&#xff0c;附件A2是相应的调查数据结果&#xff0c;附件A3是中国营养学会最新修订的《中国居民膳食指南》中为平衡居民膳食提出的…

UG NX二次开发(C#)-参数化修改三维模型中的文字

文章目录 1、前言2、在UG NX中创建一个带文字的三维模型3、创建一个UI界面4、 NXOpen二次开发实现5、测试1、前言 在UG NX中通过表达式不仅能修改尺寸参数、位置参数,也能修改文字,通过设计一个UI 界面,使得文字根据输入值的变化而变化,本文就针对三维模型中得文字来讲一下…

LeakCanary内存泄漏检测框架分析。

一、什么叫内存泄漏、内存溢出&#xff1f; 内存溢出(out of memory)&#xff1a;是指程序在申请内存时&#xff0c;没有足够的内存空间供其使用&#xff0c;出现out of memory&#xff1b;比如申请了一个integer,但给它存了long才能存下的数&#xff0c;那就是内存溢出。 内…

JavaEE——Bean的生命周期

目录 1、实例化Bean 2、设置Bean的属性 3、初始化Bean &#xff08;1&#xff09;、执行通知 &#xff08;2&#xff09;、初始化的前置方法 &#xff08;3&#xff09;、初始化方法 &#xff08;4&#xff09;、执行自定义方法 &#xff08;5&#xff09;、初始化的后置…

js读取接口返回的最快的那一个

promise.race 顾名思义&#xff0c;Promse.race就是赛跑的意思&#xff0c;意思就是说&#xff0c;Promise.race([p1, p2, p3])里面哪个结果获得的快&#xff0c;就返回那个结果&#xff0c;不管结果本身是成功状态还是失败状态。 Promise.race([this.$axios.get("api/on…

UiPath-TTS

UiPath-Text to Speech既TTS应用 缘起原理法一 - Invoke Method法二 - Invoke Code 总结 缘起 不知道大家有没有使用过Excel的Read Cell功能或者智能音箱&#xff0c;实用性因场景而异&#xff0c;但是很好玩。回到RPA应用中&#xff0c;大家想象一下&#xff0c;如果你给自己…

STM32的CAN外设简介

目录 STM32的CAN外设简介 CAN框图剖析 CAN控制内核 工作模式 位时序及波特率 CAN发送邮箱 CAN接收FIFO 验收筛选器&#xff08;重点&#xff09; 整体控制逻辑 STM32的CAN外设简介 STM32的芯片中具有bxCAN控制器 (Basic Extended CAN 基本扩展CAN外设)&#xff0c;它支…

executor.CoarseGrainedExecutorBackend: RECEIVED SIGNAL TERM

Bug现象 spark程序运行正常,但是查看web ui的时候如下图所示: 查看executor logs发现: ERROR executor.CoarseGrainedExecutorBackend: RECEIVED SIGNAL TERM原因分析 首先程序能够正常运行,只是发现某些executor状态为Dead了,说明该executor被移除了,至于为什么会被…

代码随想录算法训练营第二天| 977

977. 有序数组的平方y 思路&#xff0c;原数组是有序的&#xff0c;但是因为负数平方后可能变无序了&#xff0c;因此利用双指针遍历原数组&#xff0c;比较 nums[left]*nums[left]和nums[right]*nums[right]谁更大&#xff0c;然后对新数组赋值 class Solution {public int…

爬虫003_pycharm的安装以及使用_以及python脚本模版设置---python工作笔记021

这里我们用ide,pycharm来编码,看一看如何下载 这里我们下载这个社区办,这个是免费的,个人版是收费的 然后勾选以后 安装以后我们来创建一个项目 这里可以选择python的解释器,选择右边的... 这里我们找到我们自己安装的python解释器

SpringBoot——数据层三组件之间的关系

简单介绍 在之前的文章中&#xff0c;我们介绍了一下SpringBoot中内置的几种数据层的解决方案&#xff0c;在数据层由三部分组成&#xff0c;分别是数据库&#xff0c;持久化技术以及数据源&#xff0c;但是我今天写着写着&#xff0c;突然就想不起来这三部分到底是干什么的了…

助你丝滑过度到 Vue3 生命自定义hooktoRef ②⑥

作者 : SYFStrive 博客首页 : HomePage &#x1f4dc;&#xff1a; VUE3~TS &#x1f4cc;&#xff1a;个人社区&#xff08;欢迎大佬们加入&#xff09; &#x1f449;&#xff1a;社区链接&#x1f517; &#x1f4cc;&#xff1a;觉得文章不错可以点点关注 &#x1f449;…

TCP状态转换图

TCP状态转换图 了解TCP状态转换图可以帮助开发人员查找问题. 说明: 上图中粗线表示主动方, 虚线表示被动方, 细线部分表示一些特殊情况, 了解即可, 不必深入研究. 对于建立连接的过程客户端属于主动方, 服务端属于被动接受方(图的上半部分) 而对于关闭(图的下半部分), 服务端…

JVM内存的变化

我举得&#xff0c;从空间的角度去理解java的运行&#xff0c;能更深刻的帮助我们学习后面“面向对象”的知识。因此&#xff0c;单独拿出一章&#xff0c;来解释内存的变化。以程序为实例进行判断。 需要一点JVM运行时的内存状态知识点基础。 参考博客&#xff1a; 这种是不…

ubuntu20.04 安装 Qt5.15

目录 安装前工作 选择安装QT的哪个版本 安装时候选择哪些组件 安装Qt5.15 在线安装 我选择的组件 源码包安装 测试 安装前工作 ubuntu20.04.3安装Qt6.22操作步骤_ubuntu安装qt6_sonicss的博客-CSDN博客 # 安装g、gcc编译器 sudo apt-get install build-essential 安装l…

leetcode 455. 分发饼干

2023.7.27 今天起福州要刮台风了&#xff0c;不过还是在宿舍坚持每日一题。 今天开始刷的系列属于贪心算法系列。本题是贪心算法的一个入门题。 贪心算法总体思路就是先找局部最优&#xff0c;在一步步的找出全局最优。 本题很明显全局最优就是 需要尽可能多投喂更多的孩子。…

「双指针技巧解决一些数组问题」

文章目录 0 分类1 快慢指针刷题1.1 删除有序数组中的重复项题解Code结果 1.2 删除排序链表中的重复元素题解Code结果 1.3 移除元素题解Code结果 1.4 移动0题解Code 2 左右指针刷题2.1 二分查找2.2 两数之和 II - 输入有序数组题解Code结果 2.3 反转字符串题解Code结果 2.4 最长…