Ausführliche Erklärung zur Implementierung einer virtuellen Liste im WeChat-Applet

coldplay.xixi
Freigeben: 2020-09-07 15:48:51
nach vorne
7020 Leute haben es durchsucht

Ausführliche Erklärung zur Implementierung einer virtuellen Liste im WeChat-Applet

【Verwandte Lernempfehlungen: WeChat Mini-Programm-Tutorial

Hintergrund

Das Miniprogramm wird in vielen Szenarien auf lange Listen von Interaktionen stoßen, wenn eine Seite zu viele WXML-Knoten rendert, was dazu führt Verzögerung und weißer Bildschirm auf der Miniprogrammseite. Die Hauptgründe sind wie folgt:

1. Die Menge der Listendaten ist groß und es dauert lange, setData zu initialisieren und die Rendering-Liste wxml zu initialisieren.

2 Der virtuelle Baum und die Diff-Operation des alten Baums müssen jedes Mal erstellt werden.

3 Es werden viele WXML-Knoten gerendert Der belegte Speicher ist hoch.

Die Scroll-Ansicht des WeChat-Applets selbst ist nicht für lange Listen optimiert. Die offizielle Recycling-Ansicht der Komponente ist eine lange Listenkomponente, die der virtuellen Liste ähnelt. Jetzt analysieren wir das Prinzip der virtuellen Liste und implementieren eine virtuelle Liste eines kleinen Programms von Grund auf.

Implementierungsprinzip

Zunächst müssen wir verstehen, was eine virtuelle Liste ist. Dies ist eine Initialisierung, die nur den „sichtbaren Bereich“ und seine nahegelegenen Dom-Elemente lädt und den „sichtbaren Bereich“ nur durch Wiederverwendung von Dom-Elementen rendert während des Scrollvorgangs“ und der Scrolling-Listen-Frontend-Optimierungstechnologie der nahegelegenen Dom-Elemente. Im Vergleich zur herkömmlichen Listenmethode kann eine extrem hohe anfängliche Rendering-Leistung erzielt werden und während des Bildlaufvorgangs wird nur eine ultraleichte DOM-Struktur beibehalten.

Die wichtigsten Konzepte virtueller Listen:

  • Scrollbarer Bereich: Wenn beispielsweise die Höhe des Listencontainers 600 beträgt und die Summe der Höhen der internen Elemente die Höhe des Containers überschreitet, kann dieser Bereich sein gescrollt, was der „scrollbare Bereich“ ist der „visuelle Bereich“.

  • Der Kern der Implementierung einer virtuellen Liste besteht darin, das Scroll-Ereignis abzuhören und den oberen Abstand sowie die vorderen und hinteren Abfangindexwerte der Datenwiedergabe des „visuellen Bereichs“ durch den Scroll-Distanz-Offset und die dynamische Anpassung anzupassen Summe der Größen der gescrollten Elemente, totalSize. Die Implementierungsschritte sind wie folgt:

  • 1 Hören Sie sich das scrollTop/scrollLeft des Scroll-Ereignisses an und berechnen Sie den Indexwert startIndex des Startelements des „sichtbaren Bereichs“ und den Indexwert endIndex des Endelements; den Versatz des Elements und wenden Sie ihn auf den scrollbaren Bereich und das Element an.

1. Breite/Höhe des Listenelements und Scroll-Offset

In der virtuellen Liste wird der „scrollbare Bereich“ basierend auf der Breite/Höhe jedes Listenelements berechnet und muss möglicherweise angepasst werden. Ja, Definieren Sie die Funktion itemSizeGetter, um die Breite/Höhe des Listenelements zu berechnen.
itemSizeGetter(itemSize) {      return (index: number) => {        if (isFunction(itemSize)) {          return itemSize(index);
        }        return isArray(itemSize) ? itemSize[index] : itemSize;
      };
    }复制代码
Nach dem Login kopieren
Ausführliche Erklärung zur Implementierung einer virtuellen Liste im WeChat-AppletWährend des Scrollvorgangs wird die Elementgröße der nicht angezeigten Listenelemente nicht berechnet. Zu diesem Zeitpunkt wird eine geschätzte Listenelementgröße verwendet. Der Zweck besteht darin, die nicht gemessene Elementgröße bei der Berechnung der Höhe zu verwenden stattdessen „scrollbarer Bereich“.
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
    );
  }复制代码
Nach dem Login kopieren
Hier sehen wir, dass die itemSize und der Offset des zuletzt berechneten Listenelements direkt über den Cache abgerufen werden. Dies liegt daran, dass die beiden Parameter jedes Listenelements zwischengespeichert werden.
 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];
 }复制代码
Nach dem Login kopieren

2. Suchen Sie den Indexwert basierend auf dem Offset

Während des Scrollvorgangs müssen Sie den Indexwert der ersten im „sichtbaren Bereich“ angezeigten Daten über den Scroll-Offset-Offset berechnen 0. Die itemSize jedes Listenelements wird akkumuliert, bis sie den Offset überschreitet, und der Indexwert kann abgerufen werden. Wenn jedoch die Datenmenge zu groß ist und Scroll-Ereignisse häufig ausgelöst werden, kommt es zu einem großen Leistungsverlust. Glücklicherweise ist die Scrolldistanz der Listenelemente vollständig in aufsteigender Reihenfolge angeordnet, sodass Sie eine binäre Suche in den zwischengespeicherten Daten durchführen können, wodurch die Zeitkomplexität auf O(lgN) reduziert wird.

Der js-Code lautet wie folgt:

  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;
  }复制代码
Nach dem Login kopieren

Für die Suche ohne zwischengespeicherte Berechnungsergebnisse verwenden Sie zunächst die exponentielle Suche, um den Suchbereich einzugrenzen, und verwenden Sie dann die binäre Suche.

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,
    });
  }
}复制代码
Nach dem Login kopieren

3. Berechnen Sie startIndex und endIndex

Wir kennen die Größe des „sichtbaren Bereichs“ und den rollenden Offset-Offset. Nachdem wir die Anzahl der vorgerenderten Balken overscanCount angepasst haben, können wir den Beginn des „sichtbaren Bereichs“ berechnen. Der Indexwert startIndex des Startelements und der Indexwert endIndex des Endelements lauten wie folgt:

1 Finden Sie den Indexwert, der dem Offset am nächsten kommt. Dieser Wert ist der Indexwert startIndex des Startelements

2. Ermitteln Sie den Indexwert dieses Elements über den Offset und die Größe von startIndex und passen Sie dann den Offset an Endelementindexwert endIndex.

Der js-Code lautet wie folgt:

 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,
    };
}复制代码
Nach dem Login kopieren

3. Hören Sie sich das Scroll-Ereignis an, um einen virtuellen Listen-Scroll zu realisieren.

Jetzt können Sie einen virtuellen Listen-Scroll realisieren, indem Sie auf das Scroll-Ereignis hören und startIndex, endIndex, totalSize und offset dynamisch aktualisieren . Der

js-Code lautet wie folgt:

  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;
  },复制代码
Nach dem Login kopieren

在调用的时候,通过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);
  },
});复制代码
Nach dem Login kopieren
<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>复制代码
Nach dem Login kopieren
Ausführliche Erklärung zur Implementierung einer virtuellen Liste im WeChat-Applet

参考资料

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

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

总结

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

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

Das obige ist der detaillierte Inhalt vonAusführliche Erklärung zur Implementierung einer virtuellen Liste im WeChat-Applet. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:juejin.im
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!