Maison > Article > Applet WeChat > 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]
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
Les concepts les plus importants des listes virtuelles :
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.
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
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
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.
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: 'absolute', top: 0, left: 0, width: '100%', [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('render', { 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('#virtual-list'); }, 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>复制代码
在写这个微信小程序的virtual-list组件过程中,主要参考了一些优秀的开源虚拟列表实现方案:
通过上述解释已经初步实现了在微信小程序环境中实现了虚拟列表,并且对虚拟列表的原理有了更加深入的了解。但是对于瀑布流布局,列表项尺寸不可预测等场景依然无法适用。在快速滚动过程中,依然会出现来不及渲染而白屏,这个问题可以通过增加「可视区域」外预渲染的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!