Однажды мне стало скучно, и я захотел попрактиковаться в скорости рук, поэтому я создал список из более чем 10 000 данных о продуктах в небольшом программном проекте. После того, как данные загружены в более чем 1000 элементов, это список, который фактически имеет белый экран. Взгляните на консоль:
«Превышен лимит DOM», количество DOM превышает лимит. Я не знаю, почему WeChat думает об ограничении количества DOM на странице.
1. Сколько мини-страниц программы ограниченоwxml
узел?
Написал небольшой купол, чтобы сделать тест. Структура данных listData:
listData:[
{
isDisplay:true,
itemList:[{
qus:'下面哪位是刘发财女朋友?',
answerA:'刘亦菲',
answerB:'迪丽热巴',
answerC:'斋藤飞鸟',
answerD:'花泽香菜',
}
.......//20条数据
]
}]
Эффект рендеринга страницы:
1.dome1
<view wx:for="{{listData}}" class="first-item" wx:for-index="i" wx:for-item="firstItem" wx:key="i" wx:if="{{firstItem.isDisplay}}">
<view class="item-list" wx:for="{{firstItem.itemList}}" wx:key="index">
<view>{{item.qus}}</view>
<view class="answer-list">
<view>A. <text>{{item.answerA}}</text></view>
<view>B. <text>{{item.answerB}}</text></view>
<view>C. <text>{{item.answerC}}</text></view>
<view>D. <text>{{item.answerD}}</text></view>
</view>
</view>
</view>
2.dome2, убрать ненужную вложенность dom
<view wx:for="{{listData}}" class="first-item" wx:for-index="i" wx:for-item="firstItem" wx:key="i" wx:if="{{firstItem.isDisplay}}">
<view class="item-list" wx:for="{{firstItem.itemList}}" wx:key="index">
<view>{{item.qus}}</view>
<view class="answer-list">
<view>A. {{item.answerA}}</view>
<view>B. {{item.answerB}}</view>
<view>C. {{item.answerC}}</view>
<view>D. {{item.answerD}}</view>
</view>
</view>
</view>
По грубым подсчетам, одна страница апплета может отображать около 20 000 страниц.wxml
узелОфициальная оценка производительности апплета меньше 1000.wxml
узелОфициальная ссылка
2. Оптимизация страницы списка
1. Уменьшите количество ненужных тегов
Из вышеприведенного тестового купола видно, что при условии, что это не повлияет на выполнение и читабельность кода, минимизация вложенности тегов может значительно увеличить количество списков данных страниц, ведь компания не платит заработную плату в соответствии с количество строк кода. Если данные вашего списка ограничены, вы можете использовать этот метод, чтобы увеличить количество отображаемых элементов списка. Если объем данных очень большой, а количество узлов превышает 20 000, как бы ни упрощали, этот способ неприменим.
2. Оптимизируйте использование setData
как图五
показано, апплетsetDate
производительность будет зависеть отsetData
Размер данных и ограничение частоты вызовов. Так вращаться вокруг сокращения каждый разsetData
размер данных, уменьшитьsetData
Частота звонков для оптимизации.
(1) Удалить лишние поля
Коллеги на бэкенде часто берут данные из базы данных и возвращают их напрямую во фронтенд без какой-либо обработки, поэтому это приводит к большой избыточности данных, а многие поля вообще не используются. нужно удалить эти поля, чтобы уменьшитьsetDate
размер данных.
(2)setData
расширенное использование
Обычно, когда мы добавляем, удаляем или изменяем данные в данных, мы извлекаем исходные данные, обрабатываем их, а затем используемsetData
Чтобы обновить в целом, например, подтягивающая нагрузка, используемая в нашем списке, больше, вам нужно перейти кlistData
Добавьте данные в конце:
newList=[{...},{...}];
this.setData({
listData:[...this.data.listData,...newList]
})
Это приведет к setDate
Объем данных становится все больше и больше, а страница все больше и больше застревает.
setDate
правильная осанка
-
setDate
изменить данные
Например, мы хотим изменить массивlistData
Свойство isDisplay первого элемента мы можем сделать так:
let index=0;
this.setData({
[`listData[${index}].isDisplay`]:false,
})
Если мы хотим изменить массив одновременноlistData
Как быть с атрибутом isDisplay элементов с индексами от 0 до 9? Вы можете подумать об использовании цикла for для выполненияsetData
:
for(let index=0;index<10;index++){
this.setData({
[`listData[${index}].isDisplay`]:false,
})
}
Тогда это приводит к другой проблеме, котораяlistData
Вызов слишком частый, что также вызовет проблемы с производительностью.Правильный способ борьбы с ним — сначала собрать данные для модификации, а затем вызватьsetData
После обработки:
let changeData={};
for(let index=0;index<10;index++){
changeData[[`listData[${index}].isDisplay`]]=false;
}
this.setData(changeData);
Итак, мы помещаем массивlistData
элемент с индексами от 0 до 9isDisplay
свойство изменено наfalse
.
-
setDate
добавить данные в конец массива
Если добавляется только одна часть данных
let newData={...};
this.setData({
[`listData[${this.data.listData.length}]`]:newData
})
Если добавить несколько фрагментов данных
let newData=[{...},{...},{...},{...},{...},{...}];
let changeData={};
let index=this.data.listData.length
newData.forEach((item) => {
changeData['listData[' + (index++) + ']'] = item //赋值,索引递增
})
this.setData(changeData)
Что касается операции удаления, то лучшего способа я не нашел.
3. Используйте пользовательские компоненты
Вы можете инкапсулировать одну или несколько строк списка в пользовательский компонент и использовать компонент на странице списка, который учитывает только один узел, чтобы данные, которые может отображать ваш список, могли быть умножены. Количество узлов в компоненте тоже ограничено, но можно вкладывать компоненты послойно, чтобы добиться бесконечной загрузки списка, если не боитесь неприятностей
4. Используйте виртуальный список
После вышеуказанной серии операций производительность списка будет значительно улучшена, но если объем данных слишком велик,wxml
Количество узлов также может превысить ограничение, что приведет к сбою страницы. Наш метод обработки заключается в использовании виртуального списка, страница отображает только текущую видимую область и узлы нескольких фрагментов данных выше и ниже видимой области и управляет отображением узлов через isDisplay.
- Над видимой областью:
above
- Видимая область:
screen
- Ниже видимой области:
below
1.listData
структура массива
Используйте двумерный массив, потому что, если это одномерный массив, необходимо использовать прокрутку страницы.setData
Установить много элементовisDispaly
свойства для управления отображением списка. в то время как 2D-массив можно вызвать один разsetData
Управляйте рендерингом десяти, двадцати или даже большего количества данных.
listData:[
{
isDisplay:true,
itemList:[{
qus:'下面哪位是刘发财女朋友?',
answerA:'刘亦菲',
answerB:'迪丽热巴',
answerC:'斋藤飞鸟',
answerD:'花泽香菜',
}
.......//二维数组中的条数根据项目实际情况
]
}]
2. Необходимые параметры
data{
itemHeight:4520,//列表第一层dom高度,单位为rpx
itemPxHeight:'',//转化为px高度,因为小程序获取的滚动条高度单位为px
aboveShowIndex:0,//已渲染数据的第一条的Index
belowShowNum:0,//显示区域下方隐藏的条数
oldSrollTop:0,//记录上一次滚动的滚动条高度,判断滚动方向
prepareNum:5,//可视区域上下方要渲染的数量
throttleTime:200,//滚动事件节流的时间,单位ms
}
3.wxml
дом структура
<!-- above区域的 -->
<view class="above-box" style="height:{{aboveShowIndex*itemHeight}}rpx"> </view>
<!-- 实际渲染的区域的 -->
<view wx:for="{{listData}}" class="first-item" wx:for-index="i" wx:for-item="firstItem" wx:key="i" wx:if="{{firstItem.isDisplay}}">
<view class="item-list" wx:for="{{firstItem.itemList}}" wx:key="index">
<view>{{item.qus}}</view>
<view class="answer-list">
<view>A. {{item.answerA}}</view>
<view>B. {{item.answerB}}</view>
<view>C. {{item.answerC}}</view>
<view>D. {{item.answerD}}</view>
</view>
</view>
</view>
<!-- below区域的 -->
<view class="below-box" style="height:{{belowShowNum*itemHeight}}rpx"> </view>
4. Получите высоту в пикселях первого слоя списка.
let query = wx.createSelectorQuery();
query.select('.content').boundingClientRect(rect=>{
let clientWidth = rect.width;
let ratio = 750 / clientWidth;
this.setData({
itemPxHeight:Math.floor(this.data.itemHeight/ratio),
})
}).exec();
5. Регулировка времени прокрутки страницы
function throttle(fn){
let valid = true
return function() {
if(!valid){
return false
}
// 工作时间,执行函数并且在间隔期内把状态位设为无效
valid = false
setTimeout(() => {
fn.call(this,arguments);
valid = true;
}, this.data.throttleTime)
}
}
6. Обработка события прокрутки страницы
onPageScroll:throttle(function(e){
let scrollTop=e[0].scrollTop;//滚动条高度
let itemNum=Math.floor(scrollTop/this.data.itemPxHeight);//计算出可视区域的数据Index
let clearindex=itemNum-this.data.prepareNum+1;//滑动后需要渲染数据第一条的index
let oldSrollTop=this.data.oldSrollTop;//滚动前的scrotop,用于判断滚动的方向
let aboveShowIndex=this.data.aboveShowIndex;//获取已渲染数据第一条的index
let listDataLen=this.data.listData.length;
let changeData={}
//向下滚动
if(scrollTop-oldSrollTop>0){
if(clearindex>0){
//滚动后需要变更的条数
for(let i=aboveShowIndex;i<clearindex;i++){
changeData[[`listData[${i}].isDisplay`]]=false;
let belowShowIndex=i+2*this.data.prepareNum;
if(i+2*this.data.prepareNum<listDataLen){
changeData[[`listData[${belowShowIndex}].isDisplay`]]=true;
}
}
}
}else{//向上滚动
if(clearindex>=0){
let changeData={}
for(let i=aboveShowIndex-1;i>=clearindex;i--){
let belowShowIndex=i+2*this.data.prepareNum
if(i+2*this.data.prepareNum<=listDataLen-1){
changeData[[`listData[${belowShowIndex}].isDisplay`]]=false;
}
changeData[[`listData[${i}].isDisplay`]]=true;
}
}else{
if(aboveShowIndex>0){
for(let i=0;i<aboveShowIndex;i++){
this.setData({
[`listData[${i}].isDisplay`]:true,
})
}
}
}
}
clearindex=clearindex>0?clearindex:0
if(clearindex>=0&&!(clearindex>0&&clearindex==this.data.aboveShowIndex)){
changeData.aboveShowIndex=clearindex;
let belowShowNum=this.data.listData.length-(2*this.data.prepareNum+clearindex)
belowShowNum=belowShowNum>0?belowShowNum:0
if(belowShowNum>=0){
changeData.belowShowNum=belowShowNum
}
this.setData(changeData)
}
this.setData({
oldSrollTop:scrollTop
})
}),
После вышеуказанной обработки страницаwxml
Количество узлов относительно стабильно, и данные рендеринга страницы могут немного колебаться из-за ошибки расчета индекса данных видимой области, но оно вообще не будет превышать лимит количества узлов на странице апплета. Теоретически список из 1 миллиона фрагментов данных не будет проблемой, если у вас хватит терпения и энергии загрузить в список столько данных.
7. Что нужно оптимизировать
- Высота каждой строки списка должна быть фиксированной, иначе это вызовет ошибки в вычислении индекса данных видимой области.
- После визуализации списка воспроизведения вернитесь к списку.Если скорость руки слишком высока, данные в верхней и нижней областях не будут отображаться, и появится короткий белый экран.Проблему белого экрана можно решить.
prepareNum
,throttleTime
Два параметра улучшены, но не могут быть полностью решены (после тестирования и сравнения обнаружено, что даже если список не обрабатывается, при слишком высокой скорости скольжения появляется короткий белый экран). - Если в списке есть изображения, верхняя и нижняя области перерендериваются, хотя изображения кэшируются локально и не требуют повторного запроса с сервера, повторный рендеринг все равно занимает время, особенно когда руки очень заняты. быстрый. Согласно вышеприведенной идее,
isDisplay
уничтожать только не-<image>
Таким образом, количество узлов все равно будет увеличиваться, но оно должно удовлетворять потребности большинства проектов, в зависимости от того, что выберет ваш проект.
5. Сравнение использования пользовательских компонентов и виртуальных списков.
Хотя я не знаю почему, моя интуиция подсказывает мне, что производительность при использовании пользовательских компонентов будет относительно низкой. Чтобы сравнить преимущества и недостатки двух методов, мы использовалиTraceИнструмент выполняет тесты производительности на 5000 данных изображений полос.
Сравнение использования памяти:
Использование памяти пользовательскими компонентами:
Использование памяти виртуального списка:
Из сравнения видно, что компонент не разрушается при подтягивании и загрузке компонента, что приводит к постепенному увеличению объема данных. Виртуальный список уничтожит такое же количество данных при добавлении данных, поэтому соотношение памяти будет стабильным на определенном уровне. Для этого тестового купола 5000 фрагментов данных используют пользовательские компоненты и, наконец, занимают 2000 МБ памяти, в то время как виртуальный список стабилен на уровне 700 МБ.
Сравнение времени повторного рендеринга после setData:
Повторный рендеринг пользовательского компонента занимает много времени:
Время перерисовки виртуального списка:
Из результатов тестирования видно, что виртуальный список лучше, чем пользовательский компонент, независимо от того, является ли он трудоемким распределением, максимальным трудоемким и минимальным трудоемким.
Наконец, прикрепите адрес github виртуального списка.Если это будет вам полезно, не забудьте поставить маленькую звездочку