【react.js + hooks】useVirtualArea 渲染虚拟列表

useVirtualArea Hook

useVirtualArea 是一个 React Hook,用于创建虚拟列表。虚拟列表是一种优化技术,用于在不影响性能的情况下显示大量数据。

参数

useVirtualArea 接受一个对象和一个数组作为参数,该对象包含以下属性:

  • loadMoreItems: 一个函数,当需要加载更多数据时会被调用。
  • items: 当前的列表项。
  • hasMore: 一个布尔值,表示是否还有更多的数据可以加载。
  • height: 容器的高度。
  • style: 容器的样式。
  • containerComponent: 用于包裹列表的容器(默认div)。
  • containerComponentProps: 传递给 containerComponent 的 props。
  • renderTop: 用于渲染列表顶部的元素。
  • renderItem: 用于渲染列表项的函数。
  • itemComponent: 用于包裹列表项的容器(默认div)。
  • itemComponentProps: 传递给 itemComponent 的 props。
  • renderNoData: 没有列表数据时渲染的元素
  • renderLoader: 用于渲染加载器的容器(默认div)。
  • renderUnLoaded: 用于渲染没有更多数据时的元素。
  • loaderComponent: 用于包裹加载器的组件。
  • loaderComponentProps: 传递给 loaderComponent 的 props。
  • renderBottom: 用于渲染列表底部的元素。
  • observerOptions: 传递给 IntersectionObserver 的选项。

数组:依赖项

返回值

useVirtualArea 返回一个数组,包含以下元素:

  • loaderRef: 一个 ref,指向加载器的 DOM 元素。
  • loading: 一个布尔值,表示是否正在加载数据。
  • items: 当前的列表项。
  • render: 一个函数,用于渲染列表。

实现 useVirtualArea Hook

步骤 1:定义 Hook 和参数

首先,我们需要定义我们的 Hook 和它的参数。我们的 Hook 将接受一个对象作为参数,该对象包含我们需要的所有配置选项。

import { useState, useRef } from 'react';interface VirtualAreaOptions {loadMoreItems: () => Promise<void>;items: any[];hasMore: boolean;// ...其他参数
}export function useVirtualArea({ loadMoreItems, items, hasMore, ...rest }: VirtualAreaOptions, depths?: any[]) {// ...
}

步骤 2:定义状态和 refs

然后,我们需要定义我们的状态和 refs。我们需要一个状态来跟踪是否正在加载数据,以及一个 ref 来引用加载器的 DOM 元素。

const [loading, setLoading] = useState(false);
const loaderRef = useRef<any>(null);

步骤 3:使用 IntersectionObserver

接下来,我们需要创建一个 IntersectionObserver 来检测当加载器进入视口时。当这发生时,我们将调用 loadMoreItems 函数加载更多数据。

IntersectionObserver 是一个浏览器 API,用于异步观察目标元素与其祖先元素或顶级文档视口的交叉状态。这个 API 非常有用,因为它可以让你知道一个元素何时进入或离开视口,而无需进行复杂的计算或监听滚动事件,当被监听的元素进入视口,触发回调事件。
详见 MDN文档 - IntersectionObserver

useEffect(() => {const observer = new IntersectionObserver((entries) => {if (entries[0].isIntersecting && hasMore && !loading) {setLoading(true);loadMoreItems().then(() => {setLoading(false);});}},{ ...observerOptions });if (loaderRef.current) {observer.observe(loaderRef.current);}return () => {observer.disconnect();};
}, [loadMoreItems, hasMore, loading, observerOptions]);

步骤 4:返回值

最后,我们的 Hook 需要返回一些值。我们将返回一个数组,包含加载器的 ref、加载状态、列表项以及一个渲染函数。

return [loaderRef, loading, items, render];

在这个 render 函数中,我们将渲染所有的列表项和加载器。当 loadingtrue 时,我们将显示加载器,当 loadingfalse 并且 hasMorefalse 时,我们将显示一个表示没有更多数据的元素;当列表没有时,将展示对应的 noData 元素。

render :

const render = useCallback(() => {return (<Container {..._containerComponentProps}>{typeof renderTop === "function" ? renderTop() : renderTop}{/** @ts-ignore */(items || []).length === 0 &&(typeof renderNoData === "function"? renderNoData(): renderNoData === void 0? "No data": renderNoData)}{items.map((item, index) => (<Item key={index} {...itemComponentProps}>{typeof renderItem === "function" ? renderItem(item) : renderItem}</Item>))}{/** @ts-ignore */}<Loader ref={loaderRef} {...loaderComponentProps}>{loading &&(typeof renderLoader === "function"? renderLoader(): renderLoader === void 0? "Loading...": renderLoader)}{!loading &&!hasMore &&(typeof renderUnLoaded === "function"? renderUnLoaded(): renderUnLoaded === void 0? "No more data": renderUnLoaded)}</Loader>{typeof renderBottom === "function" ? renderBottom() : renderBottom}</Container>);}, [_containerComponentProps,renderTop,items,Item,itemComponentProps,renderItem,loaderRef,loaderComponentProps,loading,renderLoader,hasMore,renderUnLoaded,renderBottom,...(depths || []),]);

步骤5 性能优化

尽可能的使用 useMemo 和 useCallback 来提升虚拟列表的性能。

最终效果图:
在这里插入图片描述

示例代码(css代码是全局注册了@emotion, Loading是自己封装的组件):

import { useState } from "react";
import { useVirtualArea } from "@hooks/useVirtualArea";
import Loading from "@/components/Loading";
import BorderClearOutlinedIcon from "@mui/icons-material/BorderClearOutlined";function View() {const [items, setItems] = useState<any[]>([]);const [hasMore, setHasMore] = useState(true);const loadMoreItems = async () => {// Mock network requestawait new Promise((resolve) =>setTimeout(resolve, 1000 + Math.random() * 1000));// push new itemssetItems((prevItems) => [...prevItems,...Array.from({ length: 10 }, (_, i) => i + prevItems.length),]);// do not load more if there has been 50 items at leastif (items.length + 10 >= 50) {setHasMore(false);}};const renderItem = (item: any) => (<div css={$css`margin-left: 20px`}>{item}</div>);const [loaderRef, loading, _items, render] = useVirtualArea({loadMoreItems,items,hasMore,renderItem,renderNoData: (<div css={$css`display: flex; align-items: center; padding-block: 20px;`}><span>No Data</span><BorderClearOutlinedIcon style={{ marginLeft: "12px" }} /></div>),height: "300px",style: {position: "relative",},loaderComponentProps: {style: {marginBlock: "20px",},},renderTop: () => {return (<divcss={$css`display: flex; align-items: center; position: sticky; top: 0; z-index: 1; background-color: #fff; padding: 10px; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);`}><strong>total : </strong><span css={$css`margin-left: 20px;`}>{items.length}</span><strong css={$css`margin-left: 20px;`}>hasMore : </strong><span css={$css`margin-left: 20px;`}>{hasMore.toString()}</span><strong css={$css`margin-left: 20px;`}>loading : </strong><span css={$css`margin-left: 20px;`}>{loading.toString()}</span></div>);},renderLoader: () => {return (<div css={$css`display: flex; align-items: center; margin-left: 12px;`}><Loading on /><span css={$css`margin-left: 20px; color: #44A2FC;`}>Loading Items...</span></div>);},renderUnLoaded: () => {return (<div css={$css`display: flex; align-items: center;`}><span css={$css`color: #333;`}>No more Items</span><spancss={$css`margin-left: 20px;color: #44A2FC;cursor: pointer;`}onClick={() => {setItems([]);setHasMore(true);}}>Restart</span></div>);},});return <div>{render()}</div>;
}

useVirtualArea 完整实现:

import React, {useState,useEffect,useRef,useMemo,useCallback,
} from "react";export interface VirtualAreaOptions<C extends keyof React.JSX.IntrinsicElements = "div",I extends keyof React.JSX.IntrinsicElements = "div",L extends keyof React.JSX.IntrinsicElements = "div"
> {loadMoreItems: () => Promise<void>;items: any[];hasMore: boolean;height: React.CSSProperties["height"];style?: React.CSSProperties;containerComponent?: C;containerComponentProps?: React.JSX.IntrinsicElements[C];renderTop?: React.ReactNode | (() => React.ReactNode);renderItem: React.ReactNode | ((item: any) => React.ReactNode);itemComponent?: I;itemComponentProps?: React.JSX.IntrinsicElements[I];renderNoData?: React.ReactNode | (() => React.ReactNode);renderLoader?: React.ReactNode | (() => React.ReactNode);renderUnLoaded?: React.ReactNode | (() => React.ReactNode);loaderComponent?: L;loaderComponentProps?: React.JSX.IntrinsicElements[L];renderBottom?: React.ReactNode | (() => React.ReactNode);observerOptions?: IntersectionObserverInit;
}export function useVirtualArea({loadMoreItems,items,hasMore,height,style: containerStyle,renderTop,renderItem,itemComponent,itemComponentProps,renderNoData,renderLoader,renderUnLoaded,loaderComponent,loaderComponentProps,containerComponent,containerComponentProps,renderBottom,observerOptions,}: VirtualAreaOptions,depths?: any[]
) {const [loading, setLoading] = useState(false);const loaderRef = useRef<any>(null);const loadMore = useCallback(async () => {if (loading || !hasMore) return;setLoading(true);await loadMoreItems();setLoading(false);}, [loading, hasMore, loadMoreItems]);useEffect(() => {const options = {root: null,rootMargin: "20px",threshold: 1.0,};const observer = new IntersectionObserver((entries) => {if (entries[0].isIntersecting) {loadMore();}},{...options,...observerOptions,});if (loaderRef.current) {observer.observe(loaderRef.current);}return () => observer.disconnect();}, [observerOptions, loadMore]);const Container = useMemo(() => containerComponent || "div",[containerComponent]);const Item = useMemo(() => itemComponent || "div", [itemComponent]);const Loader = useMemo(() => loaderComponent || "div", [loaderComponent]);const _containerComponentProps = useMemo(() => {const { style, ...rest } = containerComponentProps ?? {};return {...rest,style: {overflow: "auto",height,...containerStyle,...style,} as React.CSSProperties,};}, [containerComponentProps, height, containerStyle]);const render = useCallback(() => {return (<Container {..._containerComponentProps}>{typeof renderTop === "function" ? renderTop() : renderTop}{/** @ts-ignore */(items || []).length === 0 &&(typeof renderNoData === "function"? renderNoData(): renderNoData === void 0? "No data": renderNoData)}{items.map((item, index) => (<Item key={index} {...itemComponentProps}>{typeof renderItem === "function" ? renderItem(item) : renderItem}</Item>))}{/** @ts-ignore */}<Loader ref={loaderRef} {...loaderComponentProps}>{loading &&(typeof renderLoader === "function"? renderLoader(): renderLoader === void 0? "Loading...": renderLoader)}{!loading &&!hasMore &&(typeof renderUnLoaded === "function"? renderUnLoaded(): renderUnLoaded === void 0? "No more data": renderUnLoaded)}</Loader>{typeof renderBottom === "function" ? renderBottom() : renderBottom}</Container>);}, [_containerComponentProps,renderTop,items,Item,itemComponentProps,renderItem,loaderRef,loaderComponentProps,loading,renderLoader,hasMore,renderUnLoaded,renderBottom,...(depths || []),]);return [loaderRef, loading, items, render] as const;
}

Bingo ! 一个用于实现虚拟列表的 useVirtualArea 就这样实现了!

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

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

相关文章

电子元器件介绍——电阻(一)

电子元器件 文章目录 电子元器件前言1.1电阻基本知识1.2电阻的作用1.3电阻的分类1.4 贴片电阻贴片电阻的规范、尺寸、封装 1.5 技术参数噪声&#xff1a; 1.6 电阻的失效 总结 前言 接下来我们就把常用的电子元器件全部介绍给大家&#xff0c;这一节是电阻&#xff0c;电容电感…

基础算法(2):排序(2):计数排序

1.计数排序实现 计数排序是一个非基于比较的稳定的线性时间的排序算法&#xff0c;而选择排序是基于比较的&#xff0c;计数排序不用&#xff0c;它的实现依靠计数。 工作原理&#xff1a;使用一个额外的数组&#xff0c;其中第i个位置是待排序数组1中值等于i的元素的个数&…

蓝桥杯物联网竞赛_STM32L071_9_按键矩阵扩展模块

原理图&#xff1a; 矩阵按键原理图&#xff1a; 实验板接口原理图&#xff1a; 得到对应图&#xff1a; 扫描按键原理&#xff1a; 按键的COLUMN1、2、3分别制0&#xff0c;每次只允许其中一个为0其他都是1&#xff08;POW1和POW2正常状况为上拉&#xff09;&#xff0c;当有…

软件设计中如何画各类图之七了解组件图:系统架构的关键视角

目录 1 前言2 组件图基本介绍3 画组件图的步骤4 组件图的用途5 场景及实际场景举例6 结语 1 前言 组件图是一种UML的图形化表示工具&#xff0c;为系统架构提供了重要视角。它描述了系统中各个组件以及它们之间的依赖关系和连接。用于展示系统中的组件、软件模块、以及它们之间…

平头哥玄铁系列 RISC-V 芯片及开发板

1、玄铁 9 系列概述 玄铁 8 系列 基于C-SKY架构&#xff0c;玄铁 9 系列基于 RISC-V 架构。E 系列为 RISC-V 32 位&#xff0c;C 系列为 RISC-V 64 位。 E902&#xff1a;超低功耗 RSIC-V 架构处理器 E902 采用 2 级极简流水线兼容 RISC-V 架构且对执行效率等方面进行了增强&a…

对于初学者来说,从哪些方面开始学习 Java 编程比较好?

对于初学者来说&#xff0c;从哪些方面开始学习 Java 编程比较好&#xff1f; 在开始前我有一些资料&#xff0c;是我根据自己从业十年经验&#xff0c;熬夜搞了几个通宵&#xff0c;精心整理了一份「Java的资料从专业入门到高级教程工具包」&#xff0c;点个关注&#xff0c;全…

玩转大数据14:分布式计算框架的选择与比较

1. 引言 随着大数据时代的到来&#xff0c;越来越多的企业和组织需要处理海量数据。分布式计算框架提供了一种有效的方式来解决大数据处理的问题。分布式计算框架将计算任务分解成多个子任务&#xff0c;并在多个节点上并行执行&#xff0c;从而提高计算效率。 2. 分布式计算…

IDEA卡顿,进行性能优化设置(亲测有效)——情况一

需求场景 IDEA重新激活后&#xff0c;运行IDEA卡的非常卡顿&#xff0c;没有运行项目&#xff0c;CPU占比也非常高: 原因分析 可能的原因是&#xff0c;在IDEA的配置中&#xff0c;给他分配的空间比较小 解决方式 步骤一 选择顶部导航栏中的Help&#xff0c;然后点击Edi…

spider小案例~https://industry.cfi.cn/BCA0A4127A4128A4141.html

一、获取列表页信息 通过抓包发现列表页信息非正常返回&#xff0c;列表信息如下图&#xff1a; 通过观察发现列表页信息是通过unes函数进行处理的&#xff0c;我们接下来去看下该函数 该函数是对列表页的信息先全局替换"~"为"%u"&#xff0c;然后再通过…

快速碰撞刚性环境的机器人低阻抗控制(阻尼影响分析)

问题描述 在快速碰撞刚性环境的机器人低阻抗控制中&#xff0c;需要通过精确的碰撞检测和处理&#xff0c;以及低阻抗控制策略的优化&#xff0c;来减少碰撞对机器人和环境的影响。同时&#xff0c;我们还需要适应刚性环境&#xff0c;提高机器人的稳定性和鲁棒性&#xff0c;…

MySQL数据库,视图、存储过程与存储函数

数据库对象&#xff1a; 常见的数据库对象&#xff1a; 视图&#xff1a; 视图是一种虚拟表&#xff0c;本身是不具有数据的占用很少的内存空间。 视图建立在已有表的基础上&#xff0c;视图赖以建立的这些表称为基表。 视图的创建和删除只影响视图本身&#xff0c;不影响对…

打造绿色计算数智动力 HashData 入选“绿色计算最具价值解决方案”

12月13日-14日&#xff0c;由绿色计算产业联盟(GCC)、边缘计算产业联盟&#xff08;ECC&#xff09;联合举办“2023计算产业生态大会”&#xff08;CIEC 2023&#xff09;在北京举行。作为计算领域的权威会议&#xff0c;本次大会邀请了多位两院院士、众多产业专家&#xff0c;…

单元测试二(实验)-云计算2023.12-云南农业大学

1、实践系列课《深入浅出Docker应用》 https://developeraliyun.com/adc/scenarioSeries/713c370e605e4f1fa7be903b80a53556?spma2c6h.27088027.devcloud-scenarioSeriesList.13.5bb75b8aZHOM2w 容器镜像的制作实验要求 创建Dockerfile文件: FROM ubuntu:latest WORKDIR data…

调用Win10隐藏的语音包

起因 在做一个文本转语音的Demo的时候&#xff0c;遇到了语音包无法正确被Unity识别的问题。明明电脑上安装了语音包但是代码就是识别不出来 原因 具体也不是非常清楚&#xff0c;但是如果语言包是在的话&#xff0c;大概率是Win10系统隐藏了。 确定语言包 首先查看%windi…

2024年天津仁爱学院高职升本科专业考试报考须知

2024年天津仁爱学院高职升本科专业考试报考须知 一、报名条件 1.报考天津仁爱学院2024年高职升本科各专业的考生&#xff0c;应符合天津市教育招生考试院制定的2024年天津市高职升本科及天津仁爱学院专业考试有关报考条件&#xff0c;须完成2024年天津市高职升本科文化考…

k8s部署nacos

先决条件: 这里的存储使用的是storageClass,所以要预先将storageClass(nfs)部署完成详情参见: k8s-StoargClass的使用-基于nfs-CSDN博客 因为nacos数据存储依赖于mysql.所以要预先部署MySQL,然后再部署nacos 部署mysql使用的pvc [rootmaster /devops/nacos/mysql]$cat mysql…

jmeter判断’响应断言‘两个变量对象是否相等

1、首先需要设置变量&#xff0c;json、正则、csv文件等变量 2、然后在响应断言中 ①JMeter Variable Name to use —— 输入一个变量&#xff0c;变量名即可 ② 模式匹配规则 ——相等 ③测试模式 ——输入引用的变量命${变量名} &#xff08;注意这里是需要添加一个测试模式…

【CANoe】CANoe中使用RS232

文章目录 1、CANoe中自带示例2、示例讲解2.1CANoe自带Port A和Port B通讯2.2CANoe自带Port A和串口助手通讯 1、CANoe中自带示例 我使用的事CANoe12&#xff0c;RS232路径如下&#xff1a; C:\Users\Public\Documents\Vector\CANoe\Sample Configurations 12.0.75\IO_HIL\RS23…

shiro入门demo(一)身份验证

shiro&#xff08;身份&#xff09;认证&#xff0c;简单来说就是登录/退出。搭建springboot项目&#xff0c;引入shiro和单元测试依赖&#xff1a; <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-…

读书笔记-《数据结构与算法》-摘要6[快速排序]

快速排序 核心&#xff1a;快排是一种采用分治思想的排序算法&#xff0c;大致分为三个步骤。 定基准——首先随机选择一个元素最为基准划分区——所有比基准小的元素置于基准左侧&#xff0c;比基准大的元素置于右侧递归调用——递归地调用此切分过程 快排的实现与『归并排…