最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

用React實(shí)現(xiàn)一個(gè)簡(jiǎn)單的虛擬列表

 更新時(shí)間:2023年12月04日 08:36:47   作者:KiraZz1  
虛擬列表是現(xiàn)在比較常用的前端渲染大數(shù)據(jù)列表的方案,目前也有很多組件庫(kù)和工具庫(kù)也都有對(duì)應(yīng)的實(shí)現(xiàn),本文將給大家介紹一下如何用React實(shí)現(xiàn)一個(gè)簡(jiǎn)單的虛擬列表,文中通過(guò)代碼示例講解的非常詳細(xì),需要的朋友可以參考下

虛擬列表是現(xiàn)在比較常用的前端渲染大數(shù)據(jù)列表的方案,目前也有很多組件庫(kù)和工具庫(kù)也都有對(duì)應(yīng)的實(shí)現(xiàn),如vueuseahooksuseVirtualList、element-plustableV2等。對(duì)于初中級(jí)前端而言,虛擬列表也是面試的??土?,和面試官聊到性能優(yōu)化的話題時(shí)有時(shí)也會(huì)涉及到。本文將筆者對(duì)虛擬列表的一些認(rèn)知,以及基于認(rèn)知給出的實(shí)現(xiàn)做下整理。雖說(shuō)現(xiàn)在網(wǎng)上寫(xiě)虛擬列表的文章非常多質(zhì)量也非常高了,但對(duì)于技術(shù)人而言,只有自己進(jìn)行輸出,才能夠有更加深刻的理解。

虛擬列表的優(yōu)勢(shì)

比如我們現(xiàn)在要渲染一萬(wàn)條數(shù)據(jù)的列表,如果直接使用循環(huán)進(jìn)行渲染,再加上列表項(xiàng)的dom結(jié)構(gòu)比較復(fù)雜的話,渲染壓力是很大的。而虛擬列表只渲染出現(xiàn)在可視區(qū)域的數(shù)據(jù),這大大減少了瀏覽器的渲染壓力。

虛擬列表的原理

這邊給出的原理僅針對(duì)于最簡(jiǎn)單的情形,即列表高度和列表項(xiàng)高度都是確定的。記列表高度為listHeight,列表項(xiàng)高度為listItemHeight。

虛擬滾動(dòng)的實(shí)現(xiàn)

在列表容器內(nèi)使用一個(gè)div撐開(kāi)滾動(dòng)條,這個(gè)div的高度應(yīng)為listItemHeight * listItemNum,其中listItemNum為數(shù)據(jù)的總量。這一步是為了模擬渲染出全部數(shù)據(jù)情形下的滾動(dòng)條。監(jiān)聽(tīng)滾動(dòng)條的事件,我們可以從列表容器的dom對(duì)象中獲取到對(duì)應(yīng)的scrollTop屬性,可以通過(guò)這個(gè)來(lái)計(jì)算渲染的起始元素索引以及渲染區(qū)域偏移量。

渲染區(qū)域的樣式

將列表容器設(shè)置為position:relative,渲染數(shù)據(jù)區(qū)域容器設(shè)置為絕對(duì)定位。我們最終希望渲染數(shù)據(jù)區(qū)域出現(xiàn)到可視區(qū)域的正確位置,所以在我們操作滾動(dòng)條時(shí)要對(duì)渲染區(qū)域的偏移量進(jìn)行計(jì)算,在計(jì)算過(guò)后把渲染區(qū)域定位到正確的位置。定位的方式有兩種,一種基于top,一種基于transform。后面給出的實(shí)現(xiàn)將是基于第二種的。

計(jì)算渲染在可視區(qū)域的數(shù)據(jù)以及渲染區(qū)域的偏移量

列表可視區(qū)域中最大允許展示的列表項(xiàng)數(shù)量為Math.ceil(listHeight / listItemHeight),記為renderDataNum

當(dāng)我們操作滾動(dòng)條時(shí),實(shí)時(shí)獲取當(dāng)前列表容器的scrollTop,可以計(jì)算得出渲染區(qū)域中的數(shù)據(jù)是從第幾個(gè)開(kāi)始的,也就是渲染數(shù)據(jù)的起始位置索引,為Math.floor(scrollTop / itemHeight),記為renderDataStartIndex。有了renderDataStartIndexrenderDataNum,我們就可以對(duì)原始數(shù)據(jù)進(jìn)行切片,獲取到渲染在可視區(qū)域的完整數(shù)據(jù)。

在這之后,我們需要調(diào)整這個(gè)區(qū)域相對(duì)于列表內(nèi)頂部的偏移量,實(shí)際上這個(gè)偏移量就是renderDataStartIndex * itemHeight。(比如現(xiàn)在要渲染[0,1,2,3,4,5],可視區(qū)域只能渲染3個(gè)元素,當(dāng)我們滑動(dòng)使元素2剛好處于列表頂部時(shí),此時(shí)的renderDataStartIndex為2,而元素2的前面還有兩個(gè)元素會(huì)占用一定的高度,這個(gè)高度就是我們剛才計(jì)算的偏移量)

緩沖區(qū)的預(yù)留

當(dāng)我們實(shí)現(xiàn)完以上幾點(diǎn)后,會(huì)發(fā)現(xiàn)列表在快速滾動(dòng)時(shí)會(huì)出現(xiàn)閃動(dòng)的狀況,即滑動(dòng)過(guò)程中底下區(qū)域會(huì)變空白。這時(shí)候我們只需要在可視區(qū)域的上下預(yù)留相應(yīng)的緩沖區(qū)數(shù)據(jù)即可。

代碼實(shí)現(xiàn)

下面是一個(gè)比較簡(jiǎn)單的用React實(shí)現(xiàn)的例子,當(dāng)然可能有bug,不過(guò)我這邊自測(cè)是沒(méi)啥問(wèn)題的:

VirtualList/index.tsx

import React, {
    CSSProperties,
    useEffect,
    useRef,
    useState,
    useCallback,
} from "react";
import "./index.css";

interface IProps<T extends { key: React.Key }> {
    height?: CSSProperties["height"];
    width?: CSSProperties["width"];
    dataSource: T[];
    itemHeight?: number;
    cacheNumber?: number;
    listItemRender?: (item: T) => React.ReactNode;
}

export const VirtualList: <T extends { key: React.Key }>(
    props: IProps<T>
) => React.ReactNode = (props) => {
    const {
        height = "500px",
        dataSource = [],
        width = "400px",
        itemHeight = 32,
        cacheNumber = 10,
        listItemRender,
    } = props;

    /** 虛擬列表外層容器 */
    const containerRef = useRef<HTMLDivElement>(null);

    /** 真正用于存放虛擬列表項(xiàng)的容器 */
    const listItemsContainerRef = useRef<HTMLDivElement>(null);

    /** 用于撐開(kāi)滾動(dòng)條的容器高度(數(shù)據(jù)項(xiàng)個(gè)數(shù) * 每個(gè)數(shù)據(jù)項(xiàng)高度) */
    const [scrollerContainerHeight, setScrollerContainerHeight] =
        useState<number>(0);

    /** 虛擬列表渲染的真實(shí)數(shù)據(jù) */
    const [renderData, setRenderData] = useState<(typeof dataSource)[number][]>(
        []
    );

    /** 渲染數(shù)據(jù)區(qū)域偏移量 */
    const [renderAreaOffset, setRenderAreaOffset] = useState<number>(0);

    /** 監(jiān)聽(tīng)滾動(dòng)條變化,觸發(fā)計(jì)算渲染數(shù)據(jù)以及渲染區(qū)域偏移量 */
    const handleScroll = useCallback(() => {
        /** 渲染元素個(gè)數(shù)(可視區(qū)域可容納最大元素?cái)?shù)量 + 緩沖區(qū)元素?cái)?shù)量) */
        const renderDataNum =
            Math.ceil((containerRef.current?.clientHeight || 0) / itemHeight) +
            cacheNumber;

        /** 渲染元素起始索引 */
        let renderDataStartIndex =
            Math.floor((containerRef.current?.scrollTop || 0) / itemHeight) -
            cacheNumber;

        if (renderDataStartIndex < 0) {
            renderDataStartIndex = 0;
        }

        setRenderData(
            dataSource.slice(
                renderDataStartIndex,
                renderDataStartIndex + renderDataNum
            )
        );

        setRenderAreaOffset(renderDataStartIndex * itemHeight);
    }, [cacheNumber, dataSource, itemHeight]);

    useEffect(() => {
        setScrollerContainerHeight(dataSource.length * itemHeight);
        handleScroll();
    }, [dataSource, handleScroll, itemHeight]);

    return (
        <div
            className="virtual-list-container"
            ref={containerRef}
            style={{ height, width }}
            onScroll={handleScroll}
        >
            <div style={{ height: `${scrollerContainerHeight}px` }}></div>
            <div
                className="virtual-list-items-container"
                ref={listItemsContainerRef}
                style={{
                    width: "100%",
                    transform: `translate(0,${renderAreaOffset}px)`,
                }}
            >
                {renderData.map((item) => (
                    <div style={{ height: `${itemHeight}px` }} key={item.key}>
                        {listItemRender
                            ? listItemRender(item)
                            : typeof item === "object"
                                ? JSON.stringify(item)
                                : (item as string)}
                    </div>
                ))}
            </div>
        </div>
    );
};

VirtualList/index.css

.virtual-list-container {
    overflow: auto;
    position: relative;
}

.virtual-list-items-container {
    position: absolute;
    top: 0;
    overflow: hidden;
}

對(duì)應(yīng)的使用例如下:

import './App.css'

import { VirtualList } from './components/VirtualList'

interface ListItemData {
  key: React.Key,
  id: string | number
  title: string
  imgSrc: string
  desc: string
}


const ListItem = (item: ListItemData) => {
  return (
    <div style={{ padding: '5px', width: '100%', boxSizing: 'border-box' }}>
      <div
        style={{ border: '1px solid #f0f0f0', boxShadow: '0 1px 2px -2px rgba(0,0,0,.16), 0 3px 6px 0 rgba(0,0,0,.12), 0 5px 12px 4px rgba(0,0,0,.09)', display: 'flex', alignItems: 'center', columnGap: 10 }}>

        <img src={item.imgSrc} style={{ height: 100 }} />
        <div style={{ display: 'flex', flexDirection: 'column',rowGap: 10  }}>
          <div style={{ fontWeight: 'bold' }}>{item.title}</div>
          <div>{item.desc}</div>
        </div>
      </div>


    </div>
  )
}


function App() {
  return (
    <div>
      <VirtualList<ListItemData> listItemRender={ListItem} itemHeight={120} dataSource={new Array(200).fill(0).map((_, index) => ({
        key: index,
        id: index,
        title: `標(biāo)題_${index}`,
        imgSrc: 'https://img14.360buyimg.com/n1/jfs/t1/83129/30/18124/355324/626b8c95Ea76bb2d9/b7c73d677df0c57d.jpg',
        desc: 'fumofumo'
      }))} />

    </div>
  )
}

export default App


效果圖:

寫(xiě)在最后

距離找到工作到現(xiàn)在已經(jīng)過(guò)去了快半年時(shí)間,或許是工作很忙,或許是進(jìn)入了舒適區(qū),學(xué)習(xí)方面的輸出相較于失業(yè)那段時(shí)間而言幾乎沒(méi)有。時(shí)間也過(guò)得很快,這一年也快過(guò)去了,未來(lái)會(huì)發(fā)生什么誰(shuí)都不好說(shuō),所以還是要戒驕戒躁,繼續(xù)努力成長(zhǎng)。

以上就是用React實(shí)現(xiàn)一個(gè)簡(jiǎn)單的虛擬列表的詳細(xì)內(nèi)容,更多關(guān)于React實(shí)現(xiàn)虛擬列表的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 詳解React中的useMemo和useCallback的區(qū)別

    詳解React中的useMemo和useCallback的區(qū)別

    React中的useMemo和useCallback是兩個(gè)重要的Hooks。常常被用于優(yōu)化組件的性能。雖然這兩個(gè)Hooks看起來(lái)很相似,但它們彼此之間還是有很大的區(qū)別的,隨著小編一起來(lái)學(xué)習(xí)吧
    2023-04-04
  • React新擴(kuò)展函數(shù)setState與lazyLoad及hook介紹

    React新擴(kuò)展函數(shù)setState與lazyLoad及hook介紹

    這篇文章主要介紹了React新擴(kuò)展函數(shù)setState與lazyLoad及hook,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧
    2022-12-12
  • 解決React報(bào)錯(cuò)Expected an assignment or function call and instead saw an expression

    解決React報(bào)錯(cuò)Expected an assignment or funct

    這篇文章主要為大家介紹了React報(bào)錯(cuò)Expected an assignment or function call and instead saw an expression解決方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • React實(shí)現(xiàn)pc端的彈出框效果

    React實(shí)現(xiàn)pc端的彈出框效果

    這篇文章主要為大家詳細(xì)介紹了React實(shí)現(xiàn)pc端的彈出框效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • React中實(shí)現(xiàn)編輯框自動(dòng)獲取焦點(diǎn)與失焦更新功能

    React中實(shí)現(xiàn)編輯框自動(dòng)獲取焦點(diǎn)與失焦更新功能

    在React應(yīng)用中,編輯框的焦點(diǎn)控制和數(shù)據(jù)回填是一個(gè)常見(jiàn)需求,本文將介紹如何使用useRef和useEffect鉤子,在組件中實(shí)現(xiàn)輸入框自動(dòng)獲取焦點(diǎn)及失焦后更新數(shù)據(jù)的功能,文中通過(guò)代碼示例給大家講解的非常詳細(xì),需要的朋友可以參考下
    2024-01-01
  • React中的Props類型校驗(yàn)和默認(rèn)值詳解

    React中的Props類型校驗(yàn)和默認(rèn)值詳解

    這篇文章主要為大家詳細(xì)介紹了React中的Props類型校驗(yàn)和默認(rèn)值,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2022-03-03
  • 一文搞懂 React 19 新特性

    一文搞懂 React 19 新特性

    React 19 引入多項(xiàng)重要更新,主要聚焦于異步狀態(tài)管理、性能優(yōu)化和開(kāi)發(fā)體驗(yàn)提升,這些改進(jìn)顯著減少了狀態(tài)管理樣板代碼,提升了應(yīng)用性能和開(kāi)發(fā)效率,感興趣的可以了解一下
    2026-05-05
  • React實(shí)現(xiàn)復(fù)雜搜索表單的展開(kāi)收起功能

    React實(shí)現(xiàn)復(fù)雜搜索表單的展開(kāi)收起功能

    本節(jié)對(duì)于需要展開(kāi)收起效果的查詢表單進(jìn)行概述,主要涉及前端樣式知識(shí)。對(duì)React實(shí)現(xiàn)復(fù)雜搜索表單的展開(kāi)-收起功能感興趣的朋友一起看看吧
    2021-09-09
  • React?props全面詳細(xì)解析

    React?props全面詳細(xì)解析

    props?是?React?組件通信最重要的手段,它在?React?的世界中充當(dāng)?shù)慕巧鞘种匾?。學(xué)好?props?可以使組件間通信更加靈活,同時(shí)文中會(huì)介紹一些?props?的操作技巧,和學(xué)會(huì)如何編寫(xiě)嵌套組件
    2022-10-10
  • react.js CMS 刪除功能的實(shí)現(xiàn)方法

    react.js CMS 刪除功能的實(shí)現(xiàn)方法

    下面小編就為大家?guī)?lái)一篇react.js CMS 刪除功能的實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-04-04

最新評(píng)論

克什克腾旗| 公主岭市| 平利县| 桐柏县| 遵义县| 黑山县| 德保县| 林甸县| 尤溪县| 新蔡县| 武夷山市| 绵竹市| 绥中县| 辉县市| 上虞市| 通许县| 辽源市| 正定县| 南宫市| 鹤山市| 南宁市| 石棉县| 十堰市| 龙海市| 城市| 铜梁县| 介休市| 鄱阳县| 冀州市| 安溪县| 广西| 呼伦贝尔市| 寿宁县| 楚雄市| 平凉市| 汕头市| 本溪| 读书| 延长县| 泾川县| 米脂县|