Maison  >  Article  >  Applet WeChat  >  Explication détaillée de la façon d'implémenter une liste virtuelle dans l'applet WeChat

Explication détaillée de la façon d'implémenter une liste virtuelle dans l'applet WeChat

coldplay.xixi
coldplay.xixiavant
2020-09-07 15:48:517053parcourir

Explication détaillée de la façon d'implémenter une liste virtuelle dans l'applet WeChat

[Recommandations d'apprentissage associées : Tutoriel du mini programme WeChat]

Contexte

Les mini-programmes rencontreront de longues listes d'interactions dans de nombreux scénarios. Lorsqu'une page affiche trop de nœuds wxml, la page du mini-programme se fige et un écran blanc apparaît. Les principales raisons sont les suivantes :

1. La quantité de données de liste est importante et l'initialisation de setData et l'initialisation de la liste de rendu wxml prennent beaucoup de temps ; Les nœuds wxml sont rendus, et chaque fois que SetData doit créer une nouvelle arborescence virtuelle pour mettre à jour la vue, et l'opération de comparaison de l'ancienne arborescence prend relativement du temps

3. wxml que la page peut accueillir est limité et la mémoire occupée est élevée.

La vue défilante de l'applet WeChat elle-même n'est pas optimisée pour les longues listes. Le composant officiel recycle-view est un composant de liste longue similaire à la liste virtuelle. Nous allons maintenant analyser le principe de la liste virtuelle et implémenter une liste virtuelle d'un petit programme à partir de zéro.

Principe d'implémentation

Tout d'abord, nous devons comprendre ce qu'est une liste virtuelle. Il s'agit d'une initialisation qui charge uniquement la "zone visible" et ses éléments dom proches, et les réutilise pendant. le processus de défilement. Une technologie d'optimisation frontale de liste déroulante qui restitue uniquement la "zone visible" et ses éléments DOM proches. Par rapport à la méthode de liste traditionnelle, elle peut atteindre des performances de rendu initiales extrêmement élevées et ne conserve qu'une structure DOM ultra-légère pendant le processus de défilement.

Les concepts les plus importants des listes virtuelles :

    Zone déroulante : Par exemple, la hauteur du conteneur de liste est de 600, et la somme des hauteurs du conteneur interne éléments dépasse la hauteur du conteneur. Cette zone peut défiler, qui est la "zone défilante"
  • Zone visible : par exemple, la hauteur du conteneur de liste est de 600 ; une barre de défilement verticale sur le côté droit pour le défilement, qui est visuellement visible. La zone interne est la « zone visuelle ».
  • Le cœur de la mise en œuvre d'une liste virtuelle est d'écouter l'événement de défilement et d'ajuster dynamiquement la distance supérieure et l'interception avant et arrière du rendu des données de la « zone visuelle » via le décalage de distance de défilement et la somme des tailles des éléments défilés, totalSize Index value, les étapes de mise en œuvre sont les suivantes :

1 Écoutez le scrollTop/scrollLeft de l'événement scroll et calculez la valeur d'index startIndex du. élément de départ et la valeur d'index endIndex de l'élément de fin dans la "zone visible"

2 .Interceptez les éléments de données de la "zone visible" de la longue liste via startIndex et endIndex, et mettez-les à jour vers le list ;

3. Calculez la hauteur de la zone de défilement et le décalage de l'élément, et appliquez-les à la zone de défilement et à l'élément.

Explication détaillée de la façon d'implémenter une liste virtuelle dans l'applet WeChat
1. La largeur/hauteur et le décalage de défilement de l'élément de la liste

Dans la liste virtuelle, dépendent de chaque élément de la liste. La largeur/hauteur est utilisée pour calculer la « zone de défilement », et elle peut devoir être personnalisée. Définissez la fonction itemSizeGetter pour calculer la largeur/hauteur de l'élément de liste.

itemSizeGetter(itemSize) {      return (index: number) => {        if (isFunction(itemSize)) {          return itemSize(index);
        }        return isArray(itemSize) ? itemSize[index] : itemSize;
      };
    }复制代码

Pendant le processus de défilement, la taille d'élément des éléments de liste qui ne sont pas apparus ne sera pas calculée. À ce moment, un élément de liste estiméestimateItemSize sera utilisé pour calculer la hauteur du "défilement". Area" sans la mesurer. La valeur itemSize qui a été transmise est remplacée parestimateItemSize.

getSizeAndPositionOfLastMeasuredItem() {    return this.lastMeasuredIndex >= 0
      ? this.itemSizeAndPositionData[this.lastMeasuredIndex]
      : { offset: 0, size: 0 };
  }

getTotalSize(): number {    const lastMeasuredSizeAndPosition = this.getSizeAndPositionOfLastMeasuredItem();    return (
      lastMeasuredSizeAndPosition.offset +
      lastMeasuredSizeAndPosition.size +
      (this.itemCount - this.lastMeasuredIndex - 1) * this.estimatedItemSize
    );
  }复制代码

Ici, nous voyons que la taille de l'élément et le décalage de l'élément de liste calculé le plus récemment sont directement touchés via le cache. En effet, les deux paramètres de chaque élément de liste sont mis en cache lorsqu'ils sont obtenus.

 getSizeAndPositionForIndex(index: number) {    if (index > this.lastMeasuredIndex) {      const lastMeasuredSizeAndPosition = this.getSizeAndPositionOfLastMeasuredItem();      let offset =
        lastMeasuredSizeAndPosition.offset + lastMeasuredSizeAndPosition.size;      for (let i = this.lastMeasuredIndex + 1; i <= index; i++) {        const size = this.itemSizeGetter(i);        this.itemSizeAndPositionData[i] = {
          offset,
          size,
        };

        offset += size;
      }      this.lastMeasuredIndex = index;
    }    return this.itemSizeAndPositionData[index];
 }复制代码

2. Recherchez la valeur d'index en fonction du décalage

Pendant le processus de défilement, vous devez calculer la valeur d'index des premières données affichées dans la "zone visible" via le décalage de défilement. offset. Dans des circonstances normales, la taille de l'élément de chaque élément de la liste peut être calculée à partir de 0, et une fois qu'elle dépasse le décalage, la valeur de l'index peut être obtenue. Cependant, lorsque la quantité de données est trop importante et que des événements de défilement sont déclenchés fréquemment, il y aura une perte de performances importante. Heureusement, la distance de défilement des éléments de la liste est entièrement organisée par ordre croissant, vous pouvez donc effectuer une recherche binaire sur les données mises en cache, réduisant ainsi la complexité temporelle à O(lgN).

Le code js est le suivant :

  findNearestItem(offset: number) {
    offset = Math.max(0, offset);    const lastMeasuredSizeAndPosition = this.getSizeAndPositionOfLastMeasuredItem();    const lastMeasuredIndex = Math.max(0, this.lastMeasuredIndex);    if (lastMeasuredSizeAndPosition.offset >= offset) {      return this.binarySearch({        high: lastMeasuredIndex,        low: 0,
        offset,
      });
    } else {      return this.exponentialSearch({        index: lastMeasuredIndex,
        offset,
      });
    }
  }

 private binarySearch({
    low,
    high,
    offset,
  }: {    low: number;
    high: number;
    offset: number;
  }) {    let middle = 0;    let currentOffset = 0;    while (low <= high) {
      middle = low + Math.floor((high - low) / 2);
      currentOffset = this.getSizeAndPositionForIndex(middle).offset;      if (currentOffset === offset) {        return middle;
      } else if (currentOffset < offset) {
        low = middle + 1;
      } else if (currentOffset > offset) {
        high = middle - 1;
      }
    }    if (low > 0) {      return low - 1;
    }    return 0;
  }复制代码

Pour les recherches qui ne mettent pas en cache les résultats de calcul, utilisez d'abord la recherche exponentielle pour restreindre la portée de la recherche, puis utilisez la recherche binaire.

private exponentialSearch({
    index,
    offset,
  }: {    index: number;
    offset: number;
  }) {    let interval = 1;    while (
      index < this.itemCount &&      this.getSizeAndPositionForIndex(index).offset < offset
    ) {
      index += interval;
      interval *= 2;
    }    return this.binarySearch({      high: Math.min(index, this.itemCount - 1),      low: Math.floor(index / 2),
      offset,
    });
  }
}复制代码

3. Calculez startIndex, endIndex

Nous connaissons la taille de la "zone visible" containersSize, le décalage de roulement, et ajustez-le en ajoutant le nombre de barres pré-rendues, overscanCount. , et vous pouvez calculer la valeur d'index startIndex de l'élément de départ et la valeur d'index endIndex de l'élément de fin dans la "zone visible". Les étapes de mise en œuvre sont les suivantes :

1. offset.Cette valeur est la valeur d'index de l'élément de départ. startIndex;

2. Obtenez le décalage et la taille de l'élément via startIndex, puis ajustez le décalage; offset vers containersSize pour obtenir le maxOffset de l'élément final et commencez à accumuler à partir de startIndex jusqu'à ce qu'au-delà de maxOffset, obtenez la valeur d'index de l'élément final endIndex.

Le code js est le suivant :

 getVisibleRange({
    containerSize,
    offset,
    overscanCount,
  }: {    containerSize: number;
    offset: number;
    overscanCount: number;
  }): { start?: number; stop?: number } {    const maxOffset = offset + containerSize;    let start = this.findNearestItem(offset);    const datum = this.getSizeAndPositionForIndex(start);
    offset = datum.offset + datum.size;    let stop = start;    while (offset < maxOffset && stop < this.itemCount - 1) {
      stop++;
      offset += this.getSizeAndPositionForIndex(stop).size;
    }    if (overscanCount) {
      start = Math.max(0, start - overscanCount);
      stop = Math.min(stop + overscanCount, this.itemCount - 1);
    }    return {
      start,
      stop,
    };
}复制代码

3. Écoutez les événements de défilement pour réaliser le défilement de la liste virtuelle

Vous pouvez désormais mettre à jour dynamiquement startIndex, endIndex, totalSize et offset. en écoutant les événements de défilement, vous pouvez réaliser un défilement de liste virtuelle.

le code js est le suivant :

  getItemStyle(index) {      const style = this.styleCache[index];      if (style) {        return style;
      }      const { scrollDirection } = this.data;      const {
        size,
        offset,
      } = this.sizeAndPositionManager.getSizeAndPositionForIndex(index);      const cumputedStyle = styleToCssString({        position: &#39;absolute&#39;,        top: 0,        left: 0,        width: &#39;100%&#39;,
        [positionProp[scrollDirection]]: offset,
        [sizeProp[scrollDirection]]: size,
      });      this.styleCache[index] = cumputedStyle;      return cumputedStyle;
  },
  
  observeScroll(offset: number) {      const { scrollDirection, overscanCount, visibleRange } = this.data;      const { start, stop } = this.sizeAndPositionManager.getVisibleRange({        containerSize: this.data[sizeProp[scrollDirection]] || 0,
        offset,
        overscanCount,
      });      const totalSize = this.sizeAndPositionManager.getTotalSize();      if (totalSize !== this.data.totalSize) {        this.setData({ totalSize });
      }      if (visibleRange.start !== start || visibleRange.stop !== stop) {        const styleItems: string[] = [];        if (isNumber(start) && isNumber(stop)) {          let index = start - 1;          while (++index <= stop) {
            styleItems.push(this.getItemStyle(index));
          }
        }        this.triggerEvent(&#39;render&#39;, {          startIndex: start,          stopIndex: stop,
          styleItems,
        });
      }      this.data.offset = offset;      this.data.visibleRange.start = start;      this.data.visibleRange.stop = stop;
  },复制代码

在调用的时候,通过render事件回调出来的startIndex, stopIndex,styleItems,截取长列表「可视区域」的数据,在把列表项目的itemSize和offset通过绝对定位的方式应用在列表上

代码如下:

let list = Array.from({ length: 10000 }).map((_, index) => index);

Page({  data: {    itemSize: index => 50 * ((index % 3) + 1),    styleItems: null,    itemCount: list.length,    list: [],
  },
  onReady() {    this.virtualListRef =      this.virtualListRef || this.selectComponent(&#39;#virtual-list&#39;);
  },

  slice(e) {    const { startIndex, stopIndex, styleItems } = e.detail;    this.setData({      list: list.slice(startIndex, stopIndex + 1),
      styleItems,
    });
  },

  loadMore() {
    setTimeout(() => {      const appendList = Array.from({ length: 10 }).map(        (_, index) => list.length + index,
      );
      list = list.concat(appendList);      this.setData({        itemCount: list.length,        list: this.data.list.concat(appendList),
      });
    }, 500);
  },
});复制代码
<view class="container">
  <virtual-list scrollToIndex="{{ 16 }}" lowerThreshold="{{50}}" height="{{ 600 }}" overscanCount="{{10}}" item-count="{{ itemCount }}" itemSize="{{ itemSize }}" estimatedItemSize="{{100}}" bind:render="slice" bind:scrolltolower="loadMore">
    <view wx:if="{{styleItems}}">
      <view wx:for="{{ list }}" wx:key="index" style="{{ styleItems[index] }};line-height:50px;border-bottom:1rpx solid #ccc;padding-left:30rpx">{{ item + 1 }}</view>
    </view>
  </virtual-list>
  {{itemCount}}</view>复制代码
Explication détaillée de la façon d'implémenter une liste virtuelle dans l'applet WeChat

参考资料

在写这个微信小程序的virtual-list组件过程中,主要参考了一些优秀的开源虚拟列表实现方案:

  • react-tiny-virtual-list
  • react-virtualized
  • react-window

总结

通过上述解释已经初步实现了在微信小程序环境中实现了虚拟列表,并且对虚拟列表的原理有了更加深入的了解。但是对于瀑布流布局,列表项尺寸不可预测等场景依然无法适用。在快速滚动过程中,依然会出现来不及渲染而白屏,这个问题可以通过增加「可视区域」外预渲染的item条数overscanCount来得到一定的缓解。

想了解更多编程学习,敬请关注php培训栏目!

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer