想要个免费网站,备案期间关闭网站,在线免费网页代理,网站信息建设总结useVirtualArea Hook
useVirtualArea 是一个 React Hook#xff0c;用于创建虚拟列表。虚拟列表是一种优化技术#xff0c;用于在不影响性能的情况下显示大量数据。
参数
useVirtualArea 接受一个对象和一个数组作为参数#xff0c;该对象包含以下属性#xff1a;
load…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: () Promisevoid;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 useRefany(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 函数中我们将渲染所有的列表项和加载器。当 loading 为 true 时我们将显示加载器当 loading 为 false 并且 hasMore 为 false 时我们将显示一个表示没有更多数据的元素当列表没有时将展示对应的 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] useStateany[]([]);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{$cssmargin-left: 20px}{item}/div);const [loaderRef, loading, _items, render] useVirtualArea({loadMoreItems,items,hasMore,renderItem,renderNoData: (div css{$cssdisplay: flex; align-items: center; padding-block: 20px;}spanNo Data/spanBorderClearOutlinedIcon style{{ marginLeft: 12px }} //div),height: 300px,style: {position: relative,},loaderComponentProps: {style: {marginBlock: 20px,},},renderTop: () {return (divcss{$cssdisplay: 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);}strongtotal : /strongspan css{$cssmargin-left: 20px;}{items.length}/spanstrong css{$cssmargin-left: 20px;}hasMore : /strongspan css{$cssmargin-left: 20px;}{hasMore.toString()}/spanstrong css{$cssmargin-left: 20px;}loading : /strongspan css{$cssmargin-left: 20px;}{loading.toString()}/span/div);},renderLoader: () {return (div css{$cssdisplay: flex; align-items: center; margin-left: 12px;}Loading on /span css{$cssmargin-left: 20px; color: #44A2FC;}Loading Items.../span/div);},renderUnLoaded: () {return (div css{$cssdisplay: flex; align-items: center;}span css{$csscolor: #333;}No more Items/spanspancss{$cssmargin-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 VirtualAreaOptionsC extends keyof React.JSX.IntrinsicElements div,I extends keyof React.JSX.IntrinsicElements div,L extends keyof React.JSX.IntrinsicElements div{loadMoreItems: () Promisevoid;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 useRefany(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 就这样实现了