簡單寫一個react組件吧----虛擬列表

參考:高性能渲染十萬條數(shù)據(jù)(虛擬列表)

1. 應用場景

需要用列表的形式展示大量的數(shù)據(jù),本文章只針對規(guī)則的、等高且固定高度的列表。如圖:


虛擬列表

前端渲染大量數(shù)據(jù)時會造成頁面卡頓,原因之一是渲染的DOM節(jié)點太多,而虛擬列表只渲染可見區(qū)域的DOM節(jié)點,極大的優(yōu)化了渲染性能。

2. 思路

初次加載時,只渲染初始的一部分數(shù)據(jù),頁面滾動時,動態(tài)計算需要展示的數(shù)據(jù)和滾動的位置。為此,DOM的設(shè)計需要三個區(qū)域----容器、列表展示區(qū)域、支撐滾動條區(qū)域

  • 容器:包裹列表展示區(qū)和滾動條支撐區(qū);
  • 列表展示區(qū)域:真實渲染的列表項區(qū)域,也就是可見的列表項部分;
  • 支撐滾動條區(qū)域:用于支撐容器的高度,使容器出現(xiàn)滾動條。
    樣式的命名可以個性化一點......
<div class="jisl-container">
    <div class="phantom"></div>
    <div class="view">
      <!-- item-1 -->
      <!-- item-2 -->
      <!-- ...... -->
      <!-- item-n -->
    </div>
</div>

3. 代碼實現(xiàn)

目錄結(jié)構(gòu),新建一個文件夾,然后在文件夾中新建js和css文件

VirtualList\
    index.js
    style.css

在index.js中編輯代碼

import React, {useState, useEffect, useRef} from 'react';
import './style.css';

const VirtualList = (props) => {
  const scrollRef = useRef(); // 滾動條ref

  const {
    data,       // 渲染的數(shù)據(jù)
    count,      // 列表的數(shù)量、長度
    size,       // 可視區(qū)渲染的列表項數(shù)量(真實DOM節(jié)點數(shù)量)
    viewSize,   // 可視區(qū)能看到的列表數(shù)量, 數(shù)值比size小, 即DOM比可見數(shù)量多, 具有緩沖作用
    rowHeight,  // 每一行列表項的高度
    renderNode, // 渲染的列表項DOM節(jié)點 
  } = props;

  const [startIndex, setStartIndex] = useState(0);       // 起始索引
  const [phantomHeight, setPhantomHeight] = useState(0); // 占位區(qū)的高度
  const [startOffset, setStartOffset] = useState(0);     // 渲染區(qū)域偏移量

  // 計算支撐滾動條區(qū)域的高度
  useEffect(() => {
    setPhantomHeight(rowHeight * count);
  }, [count, rowHeight])

  /**
   * 滾動時更新顯示區(qū)域的數(shù)據(jù)和高度
   * @param {DOM.event} e 
   */
  const onScroll = e => {
    let scrollTop = e.target.scrollTop;               
    let offset = scrollTop - (scrollTop % rowHeight);
    let index = Math.floor(scrollTop / rowHeight); 
    setStartOffset(offset);
    setStartIndex(index);   
  }

  return (
      <div 
        className="jisl-container"
        style={
          (data && data.length > viewSize) || (data && data.length === 0)
          ? { height: rowHeight * viewSize } 
          : { height: rowHeight * data.length }
        }
        onScroll={onScroll}      
      >
        <div className="phantom" style={{height: phantomHeight}} />
        <div 
          className="view"
          style={{transform: `translateY(${startOffset}px)`}}                
        >      
          {
            data instanceof Array && data.length > 0
            ? data.slice(startIndex, startIndex + size).map((item, index) => {
                if(Object.prototype.toString.call(renderNode) !== '[object Function]') return; 
                return renderNode(data, item, index + startIndex);
              })
            : <div />
          }                
        </div>
      </div>
  )
}

export default VirtualList;

在style.css中編寫樣式

  // 外層容器 
  .jisl-container {
    position: relative;
    width: 100%;

    overflow-y: auto;
    overflow-x: hidden;
    background: #fff;
    box-shadow: 0 2px 5px -2px rgba(0,0,0,.05), 
                0 4px 10px 0 rgba(0,0,0,.08),
                0 6px 20px 4px rgba(0,0,0,.05);
  }

  // 支撐區(qū)域
  .phantom {
    width: 100%;
    background: #fff;
  }

  // 可視區(qū)列表項 
  .view {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 100%;
    background: #fff;
  } 
  • 外層容器設(shè)置overflow,只展示可見區(qū)域,并且position設(shè)置relative。每一個列表項高度rowHeight設(shè)置32px,顯示數(shù)量viewSize設(shè)置5個,外層容器的高度為rowHeight * viewSize;
  • 支撐區(qū)域的高度固定,總的列表項數(shù)目是count,那么支撐區(qū)域高度為rowHeight * count;
  • 可視區(qū)域position設(shè)置absolute脫離文檔流,然后計算偏移量,使用transform跟隨滾動條移動位置;
  • 其中監(jiān)聽onscroll事件的邏輯最為關(guān)鍵
    單獨截取出來
  /**
   * 滾動時更新顯示區(qū)域的數(shù)據(jù)和高度
   * @param {DOM.event} e 
   */
  const onScroll = e => {
    let scrollTop = e.target.scrollTop;               
    let offset = scrollTop - (scrollTop % rowHeight);
    let index = Math.floor(scrollTop / rowHeight); 
    setStartOffset(offset);
    setStartIndex(index);   
  }

首先是獲取當前滾動條的位置

  let scrollTop = e.target.scrollTop;    

滾動條位置變化時,計算渲染區(qū)域的偏移量

  let offset = scrollTop - (scrollTop % rowHeight);
  setStartOffset(offset);

計算展示的數(shù)據(jù)的索引

  let index = Math.floor(scrollTop / rowHeight); 
  setStartIndex(index);  

數(shù)組的slice方法不會改變原數(shù)組,所以渲染時直接用slice方法截取,size是渲染的DOM數(shù)量,size比可視區(qū)域的列表項viewSize大一點可以起到緩沖作用

data.slice(startIndex, startIndex + size)

封裝好之后的使用方法如下

import React from 'react';

const test = () => {
  const data = ['這是一個數(shù)組'];  

  const renderNode = (data, item, index) => {
    return <div key={index}  onClick={() => console.log(data)}> { item } </div>
  }

  return (
    <VirtualList 
      data={ data }              // 總數(shù)據(jù)
      count={ data.length }      // 列表項數(shù)量
      size={ 8 }                 // 可視區(qū)渲染DOM的列表項數(shù)量
      viewSize={ 5 }             // 可視區(qū)能看到的列表數(shù)量
      rowHeight={ 32 }           // 每個列表項的行高度
      renderNode={ renderNode }  // 渲染的每個列表項
    />
  )
}

export default test;

結(jié)合下拉框使用的效果...


效果
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容