В этом посте мы рассмотрим, как оптимизировать производительность ListView NativeScript с помощью шаблонов с несколькими элементами. Примеры кода нацелены на NativeScript с Angular, но те же идеи применимы, если вы используете NativeScript без Angular.
Фон
Под капотом NativeScript использует собственные элементы управления списком для визуализации ListView. Плавная, как масло прокрутка в этих элементах управления зависит от двух основных функций:
- Виртуализация пользовательского интерфейса - представления создаются только для тех элементов, которые в данный момент видны. Меньше элементов в памяти = ›меньше памяти.
- Повторное использование представления - всякий раз, когда элемент прокручивается из порта просмотра, представление, которое его отображало, не уничтожается. Он помещается в пул переработанных представлений и повторно используется, когда появляется новый элемент.
Рендеринг различных элементов
Иногда нам нужно отрендерить коллекцию с разными элементами, и нам нужно использовать для них разные шаблоны. Допустим, мы показываем коллекцию новостей. Некоторые предметы большие, некоторые маленькие, а некоторые мелкие имеют изображения, а некоторые нет.
Использование единого шаблона
Если вы какое-то время использовали angular, у вас может возникнуть соблазн применить ngIf/ngSwitch
магию. Вы, вероятно, получите что-то вроде этого:
<ListView [items]="items"> <ng-template let-item="item"> <StackLayout> <GridLayout *ngIf="item.type === 'big'"> <!-- big item template --> </GridLayout> <GridLayout *ngIf="item.type === 'small' && item.imageUrl"> <!-- small item with image --> </GridLayout> <GridLayout *ngIf="item.type === 'small' && !item.imageUrl"> <!-- small item with no image --> </GridLayout> </StackLayout> </ng-template> </ListView>
Здесь происходит то, что переработка отходов больше не работает должным образом.
Допустим, большой элемент прокручивается из поля зрения, а его шаблон представления помещается в пул корзины, а затем повторно используется как элемент маленькое изображение. Модуль рендеринга angular должен будет создать все представления, содержащиеся во втором ngIf
, и уничтожит все представления из первого ngIf
. Единственной повторно используемой частью будет упаковка StackLayout
(которая на самом деле не нужна).
Это означает, что это будет медленным и в то же время генерировать мусор. Вот как это выглядит:
Это было медленно! Мы ясно видим, как GC останавливает прокрутку.
Лучший подход
К счастью, есть лекарство. Существует способ указать ListView использовать различные шаблоны элементов в зависимости от заданных вами критериев элементов. Хорошая часть состоит в том, что он будет хранить переработанные представления в разных пулах и повторно использовать представление из правильного пула, когда это необходимо для рендеринга следующего элемента. Единственное, что нужно будет изменить, это привязки - не создавать / уничтожать представления.
Нет необходимости в ngIf и, следовательно, нет чрезмерного создания / уничтожения представлений пользовательского интерфейса каждый раз, когда представление повторно используется. В качестве бонуса мы можем избавиться от StackLayout
, который раньше просто содержал 3 разных шаблона.
И вот код для этого:
<ListView [items]="items" [itemTemplateSelector]="templateSelector"> <ng-template nsTemplateKey="big" let-item="item"> <!-- big item template --> </ng-template> <ng-template nsTemplateKey="small" let-item="item"> <!-- small item with image --> </ng-template> <ng-template nsTemplateKey="small-no-image" let-item="item"> <!-- small item with no image --> </ng-template> </ListView>
В разметке мы определяем 3 разных <template>
элемента, дающих каждому имя с помощью директивы nsTemplateKey. Мы также даем ListView функцию itemTemplateSelector, которая определена в коде компонента. Он должен возвращать имя шаблона, который будет использоваться с фактическим элементом:
public templateSelector(item: NewsItem, index: number, items: NewsItem[]) { if (item.type === "big") { return "big" } if (item.type === "small" && item.imageUrl) { return "small"; } if (item.type === "small" && item.imageUrl) { return "small-no-image"; } throw new Error("Unrecognized template!") }
Посмотрим, как это ведет себя:
Аккуратный! Это именно то исполнение, которое мы ищем!
Заключение
Вот код как для сверхмедленного * ngIf шаблона, так и для сверхбыстрого селектора шаблонов. Заключительные мысли:
- ngIf в шаблонах элементов ListView: 👎🐌👎
- Несколько шаблонов + селектор шаблонов: 👍🚀👍