【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,一经查实,立即删除!

相关文章

Android : Room 数据库的基本用法 —简单应用_三_版本

在实体类中添加了新字段&#xff1a; Entity(tableName "people") public class People {//新添加的字段private String email;public String getEmail() {return email;}public void setEmail(String email) {this.email email;}} 再次编译启动时会报错&#xf…

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

电子元器件 文章目录 电子元器件前言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…

linux-tar命令、解压、压缩

压缩 文件夹 命令&#xff1a;tar -zcvf ~/test/tar_t.tar.gz /target/ 将/target/文件夹及其子文件夹和文件压缩成tar_t.tar.gz文件&#xff0c;并放于~/test/路径下 文件 命令&#xff1a;tar -zcvf ~/test/tar_t.tar.gz /target/file 将/target/file文件压缩成tar_t.tar…

linux中文件服务器NFS和FTP服务

文件服务器 NFSNFS介绍配置nfs文件共享服务端客户端 FTPftp介绍FTP基础ftp主动模式ftp被动模式 Vsftp 服务器简介vsftpd配置安装vsftpd[ftp的服务端]编辑配置文件匿名用户设置创建本地用户使用ftp服务 客户端操作匿名用户登录本地用户登录lftp服务 NFS NFS介绍 文件系统级别共…

对于初学者来说,从哪些方面开始学习 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…

Webpack cl4 配置

基本配置&#xff1a; module.exports defineConfig({transpileDependencies: false,lintOnSave: false,outputDir: process.env.VUE_APP_DIST, // 打包后文件的目录publicPath: ./, // 静态资源路径&#xff08;默认/&#xff0c;打包后会白屏&#xff09;assetsDir: static…

在CentOS中安装docker

环境&#xff1a;CentOS 7 目的&#xff1a;安装docker、启动服务 依据&#xff1a; &#x1f517;官方文档 一、前提 1、查看内核版本 uname -rTip&#xff1a;docker需要内核版本3.10以上。所以CentOS 7是最低要求 2、更新软件包 耗时较长 yum update -y3、安装yum…

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…

优先考虑基于任务的编程而非基于线程的编程

优先考虑基于任务的编程而非基于线程的编程 ”优先考虑基于任务的编程而非基于线程的编程。对比基于线程的编程方式&#xff0c;基于任务的设计为开发者避免了手动线程管理的痛苦&#xff08;这种调用方式将线程管理的职责转交给C标准库的开发者&#xff09;&#xff0c;并且自…