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

ahooks useVirtualList 封裝虛擬滾動(dòng)列表

 更新時(shí)間:2022年09月09日 10:53:40   作者:Gopal  
這篇文章主要為大家介紹了ahooks useVirtualList 封裝虛擬滾動(dòng)列表詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

簡(jiǎn)介

提供虛擬化列表能力的 Hook,用于解決展示海量數(shù)據(jù)渲染時(shí)首屏渲染緩慢和滾動(dòng)卡頓問(wèn)題。

詳情可見官網(wǎng),文章源代碼可以點(diǎn)擊這里。

實(shí)現(xiàn)原理

其實(shí)現(xiàn)原理監(jiān)聽外部容器的 scroll 事件以及其 size 發(fā)生變化的時(shí)候,觸發(fā)計(jì)算邏輯算出內(nèi)部容器的高度和 marginTop 值。

具體實(shí)現(xiàn)

其監(jiān)聽滾動(dòng)邏輯如下:

// 當(dāng)外部容器的 size 發(fā)生變化的時(shí)候,觸發(fā)計(jì)算邏輯
useEffect(() => {
  if (!size?.width || !size?.height) {
    return;
  }
  // 重新計(jì)算邏輯
  calculateRange();
}, [size?.width, size?.height, list]);
// 監(jiān)聽外部容器的 scroll 事件
useEventListener(
  'scroll',
  e => {
    // 如果是直接跳轉(zhuǎn),則不需要重新計(jì)算
    if (scrollTriggerByScrollToFunc.current) {
      scrollTriggerByScrollToFunc.current = false;
      return;
    }
    e.preventDefault();
    // 計(jì)算
    calculateRange();
  },
  {
    // 外部容器
    target: containerTarget,
  },
);

其中 calculateRange 非常重要,它基本實(shí)現(xiàn)了虛擬滾動(dòng)的主流程邏輯,其主要做了以下的事情:

  • 獲取到整個(gè)內(nèi)部容器的高度 totalHeight。
  • 根據(jù)外部容器的 scrollTop 算出已經(jīng)“滾過(guò)”多少項(xiàng),值為 offset。
  • 根據(jù)外部容器高度以及當(dāng)前的開始索引,獲取到外部容器能承載的個(gè)數(shù) visibleCount。
  • 并根據(jù) overscan(視區(qū)上、下額外展示的 DOM 節(jié)點(diǎn)數(shù)量)計(jì)算出開始索引(start)和(end)。
  • 根據(jù)開始索引獲取到其距離最開始的距離(offsetTop)。
  • 最后根據(jù) offsetTop 和 totalHeight 設(shè)置內(nèi)部容器的高度和 marginTop 值。

變量很多,可以結(jié)合下圖,會(huì)比較清晰理解:

代碼如下:

// 計(jì)算范圍,由哪個(gè)開始,哪個(gè)結(jié)束
const calculateRange = () => {
  // 獲取外部和內(nèi)部容器
  // 外部容器
  const container = getTargetElement(containerTarget);
  // 內(nèi)部容器
  const wrapper = getTargetElement(wrapperTarget);
  if (container && wrapper) {
    const {
      // 滾動(dòng)距離頂部的距離。設(shè)置或獲取位于對(duì)象最頂端和窗口中可見內(nèi)容的最頂端之間的距離
      scrollTop,
      // 內(nèi)容可視區(qū)域的高度
      clientHeight,
    } = container;
    // 根據(jù)外部容器的 scrollTop 算出已經(jīng)“滾過(guò)”多少項(xiàng)
    const offset = getOffset(scrollTop);
    // 可視區(qū)域的 DOM 個(gè)數(shù)
    const visibleCount = getVisibleCount(clientHeight, offset);
    // 開始的下標(biāo)
    const start = Math.max(0, offset - overscan);
    // 結(jié)束的下標(biāo)
    const end = Math.min(list.length, offset + visibleCount + overscan);
    // 獲取上方高度
    const offsetTop = getDistanceTop(start);
    // 設(shè)置內(nèi)部容器的高度,總的高度 - 上方高度
    // @ts-ignore
    wrapper.style.height = totalHeight - offsetTop + 'px';
    // margin top 為上方高度
    // @ts-ignore
    wrapper.style.marginTop = offsetTop + 'px';
    // 設(shè)置最后顯示的 List
    setTargetList(
      list.slice(start, end).map((ele, index) => ({
        data: ele,
        index: index + start,
      })),
    );
  }
};

其它就是這個(gè)函數(shù)的輔助函數(shù)了,包括:

  • 根據(jù)外部容器以及內(nèi)部每一項(xiàng)的高度,計(jì)算出可視區(qū)域內(nèi)的數(shù)量:
// 根據(jù)外部容器以及內(nèi)部每一項(xiàng)的高度,計(jì)算出可視區(qū)域內(nèi)的數(shù)量
const getVisibleCount = (containerHeight: number, fromIndex: number) => {
  // 知道每一行的高度 - number 類型,則根據(jù)容器計(jì)算
  if (isNumber(itemHeightRef.current)) {
    return Math.ceil(containerHeight / itemHeightRef.current);
  }
  // 動(dòng)態(tài)指定每個(gè)元素的高度情況
  let sum = 0;
  let endIndex = 0;
  for (let i = fromIndex; i < list.length; i++) {
    // 計(jì)算每一個(gè) Item 的高度
    const height = itemHeightRef.current(i, list[i]);
    sum += height;
    endIndex = i;
    // 大于容器寬度的時(shí)候,停止
    if (sum >= containerHeight) {
      break;
    }
  }
  // 最后一個(gè)的下標(biāo)減去開始一個(gè)的下標(biāo)
  return endIndex - fromIndex;
};
  • 根據(jù) scrollTop 計(jì)算上面有多少個(gè) DOM 節(jié)點(diǎn):
// 根據(jù) scrollTop 計(jì)算上面有多少個(gè) DOM 節(jié)點(diǎn)
const getOffset = (scrollTop: number) => {
  // 每一項(xiàng)固定高度
  if (isNumber(itemHeightRef.current)) {
    return Math.floor(scrollTop / itemHeightRef.current) + 1;
  }
  // 動(dòng)態(tài)指定每個(gè)元素的高度情況
  let sum = 0;
  let offset = 0;
  // 從 0 開始
  for (let i = 0; i < list.length; i++) {
    const height = itemHeightRef.current(i, list[i]);
    sum += height;
    if (sum >= scrollTop) {
      offset = i;
      break;
    }
  }
  // 滿足要求的最后一個(gè) + 1
  return offset + 1;
};
  • 獲取上部高度:
// 獲取上部高度
const getDistanceTop = (index: number) => {
  // 每一項(xiàng)高度相同
  if (isNumber(itemHeightRef.current)) {
    const height = index * itemHeightRef.current;
    return height;
  }
  // 動(dòng)態(tài)指定每個(gè)元素的高度情況,則 itemHeightRef.current 為函數(shù)
  const height = list
    .slice(0, index)
    // reduce 計(jì)算總和
    // @ts-ignore
    .reduce((sum, _, i) => sum + itemHeightRef.current(i, list[index]), 0);
  return height;
};
  • 計(jì)算總的高度:
// 計(jì)算總的高度
const totalHeight = useMemo(() => {
  // 每一項(xiàng)高度相同
  if (isNumber(itemHeightRef.current)) {
    return list.length * itemHeightRef.current;
  }
  // 動(dòng)態(tài)指定每個(gè)元素的高度情況
  // @ts-ignore
  return list.reduce(
    (sum, _, index) => sum + itemHeightRef.current(index, list[index]),
    0,
  );
}, [list]);

最后暴露一個(gè)滾動(dòng)到指定的 index 的函數(shù),其主要是計(jì)算出該 index 距離頂部的高度 scrollTop,設(shè)置給外部容器。并觸發(fā) calculateRange 函數(shù)。

// 滾動(dòng)到指定的 index
const scrollTo = (index: number) => {
  const container = getTargetElement(containerTarget);
  if (container) {
    scrollTriggerByScrollToFunc.current = true;
    // 滾動(dòng)
    container.scrollTop = getDistanceTop(index);
    calculateRange();
  }
};

思考總結(jié)

對(duì)于高度相對(duì)比較確定的情況,我們做虛擬滾動(dòng)還是相對(duì)簡(jiǎn)單的,但假如高度不確定呢?

或者換另外一個(gè)角度,當(dāng)我們的滾動(dòng)不是縱向的時(shí)候,而是橫向,該如何處理呢?

以上就是ahooks useVirtualList 封裝虛擬滾動(dòng)列表的詳細(xì)內(nèi)容,更多關(guān)于ahooks useVirtualList封裝的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 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
  • ReactHooks批量更新state及獲取路由參數(shù)示例解析

    ReactHooks批量更新state及獲取路由參數(shù)示例解析

    這篇文章主要介紹了React Hooks如何實(shí)現(xiàn)批量更新state以及獲取路由參數(shù)的示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2021-10-10
  • react native之ScrollView下拉刷新效果

    react native之ScrollView下拉刷新效果

    這篇文章主要為大家詳細(xì)介紹了react native之ScrollView下拉刷新效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • React組件實(shí)例三大核心屬性State props Refs詳解

    React組件實(shí)例三大核心屬性State props Refs詳解

    組件實(shí)例的三大核心屬性是:State、Props、Refs。類組件中這三大屬性都存在。函數(shù)式組件中訪問(wèn)不到 this,也就不存在組件實(shí)例這種說(shuō)法,但由于它的特殊性(函數(shù)可以接收參數(shù)),所以存在Props這種屬性
    2022-12-12
  • React中的函數(shù)式插槽詳解

    React中的函數(shù)式插槽詳解

    這篇文章主要為大家詳細(xì)介紹了React?開發(fā)中遇到的具名插槽的函數(shù)用法,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,有興趣的小伙伴可以了解一下
    2023-11-11
  • React組件之多選Checkbox實(shí)例

    React組件之多選Checkbox實(shí)例

    這篇文章主要介紹了React組件之多選Checkbox實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,
    2023-10-10
  • React?中的?JS?報(bào)錯(cuò)及容錯(cuò)方案

    React?中的?JS?報(bào)錯(cuò)及容錯(cuò)方案

    這篇文章主要為大家介紹了React?中的?JS?報(bào)錯(cuò)及容錯(cuò)方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08
  • react?redux的原理以及基礎(chǔ)使用講解

    react?redux的原理以及基礎(chǔ)使用講解

    這篇文章主要介紹了react?redux的原理以及基礎(chǔ)使用講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • React和Vue的props驗(yàn)證示例詳解

    React和Vue的props驗(yàn)證示例詳解

    這篇文章主要介紹了React和Vue的props驗(yàn)證,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-08-08
  • react數(shù)據(jù)管理機(jī)制React.Context源碼解析

    react數(shù)據(jù)管理機(jī)制React.Context源碼解析

    這篇文章主要為大家介紹了react數(shù)據(jù)管理機(jī)制React.Context源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11

最新評(píng)論

武山县| 沙河市| 天津市| 汤阴县| 岗巴县| 定州市| 台北市| 新河县| 甘孜县| 鹤壁市| 武邑县| 甘泉县| 潼关县| 革吉县| 明光市| 塔河县| 清涧县| 丽江市| 广宁县| 临沭县| 天台县| 台东县| 库车县| 尼木县| 通山县| 方正县| 特克斯县| 称多县| 西昌市| 稷山县| 潍坊市| 鄯善县| 福清市| 云浮市| 射阳县| 黄石市| 阳原县| 井陉县| 仪征市| 临沂市| 崇信县|