Angular функция trackby с использованием неопределенных побочных эффектов - PullRequest
1 голос
/ 27 марта 2020

У меня проблема с слишком частым обнаружением изменений в одном из моих компонентов, поэтому я пытаюсь использовать опцию trackBy для директивы ngFor.

При чтении я понимаю, что Angular будет использовать значение, возвращаемое вашей функцией trackyBy, для его сравнения при следующем запуске обнаружения изменений. Чтобы увидеть, соответствует ли это моим потребностям, и чтобы попытаться понять это лучше, я создал игровую площадку. При его использовании я устанавливаю возвращаемое значение функции trackyBy, которую я использую, чтобы возвратить неопределенное, и я все еще получаю результаты, которые я хотел.

TS:

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  styleUrls: ['./app.component.scss'],
  templateUrl: './app.component.html',
})
export class AppComponent {
collection;
  constructor() {
    this.collection = [{id: 1, value: 0}, {id: 2, value: 0}, {id: 3, value: 0}];
  }

  getItems() {
    this.collection = this.getItemsFromServer();
  }

  getItemsFromServer() {
    return [{id: 5, value: 0}, {id: 2, value: 0}, {id: 3, value: 3}, {id: 4, value: 4}];
  }

  trackByFn(index, item) {
    return undefined;
  }
}

HTML:

    <ul>
      <li *ngFor="let item of collection;trackBy: trackByFn">{{ item.id }}hello {{item.value}}</li>
    </ul>
    <button (click)="getItems()">Refresh items</button>

Результатом первого щелчка будет то, что все элементы будут переопределены с новым значением или идентификатором, кроме индекса 1 массива. При втором щелчке ни один из элементов не переопределяется. B c внутри объектов ничего не меняется.

Итак, мой вопрос: зачем вообще использовать уникальный идентификатор для возвращаемого значения функции trackBy? Должно быть что-то, чего мне не хватает, и я не хочу, чтобы это влияло на мое приложение так, как я пока не вижу.

Ответы [ 2 ]

2 голосов
/ 30 марта 2020

Официальный ответ заключается в том, что вы используете trackBy, чтобы избежать воссоздания элементов в DOM для новых экземпляров объектов с одинаковым идентификатором.

На первый взгляд, ваша установка не доказывает, что ngFor не просто игнорирует значение undefined, которое вы возвращаете в trackByFn, и воссоздает элементы DOM в любом случае, когда в модели появляются новые экземпляры.

Новые элементы с тем же идентификатором, что и существующие объекты, могут изменились другие свойства, поэтому можно ожидать, что HTML будет правильным независимо от того, используете ли вы (или неправильно) trackBy.

Настройка

, которую я создал тестовая среда, использующая ваш код, за исключением того, что я разветвил исходный код для *ngFor, чтобы я мог добавить собственную регистрацию, чтобы отслеживать, что происходит внутри *ngFor.

Я тестировал три сценария ios:

A) trackByFn возвращает уникальный идентификатор

B) trackByFn возвращает undefined

C) не использует trackBy

Я проследил, что происходит в каждом сценарии для следующих шагов

* 1 031 * создать список частично заменить некоторые данные списка список сортировки сбросить данные списка

Я назначил новые экземпляры объектов на каждом шаге для «чистого» теста.

Результаты

1. создать список

То же самое для всех 3 сценариев ios - элемент DOM создается для каждого элемента в списке.

2. частично заменить некоторые данные списка

A) удаляет элементы DOM для удаленных элементов и создает новые элементы DOM для новых элементов. Все элементы обновляются.

B) создает новые элементы DOM для элементов с индексом за пределами исходного массива. Все элементы обновляются.

C) воссоздает все элементы DOM.

3. список сортировки

A) перемещает элементы DOM, которые сместили позиции

B) обновляет элементы DOM, которые сместили позиции

C), воссоздает все DOM элементы

4. сбросить список данных

A) удаляет элементы DOM для удаленных элементов и создает новые элементы DOM для новых элементов. Все элементы обновляются (как в сценарии 2).

B) удаляет элементы DOM для удаленных элементов. Все элементы обновляются.

C) воссоздает все элементы DOM.

Выводы

Важно отметить, что эти тесты были выполнены с использованием новых экземпляров объектов. *ngFor более эффективен, если вы повторно используете ссылки на объекты.

Использование trackBy более эффективно с точки зрения манипулирования DOM, если у вас очень изменчивый список.

Удивительно результат

Из моих тестов выясняется, что ваш пример выполняет меньше манипуляций с DOM, чем при возврате уникального идентификатора из trackByFn. Если вы замените 3 элемента на 3 новых, ваш метод не будет манипулировать DOM и все равно будет запускать тот же метод обновления, что и «правильный» способ. «Правильный» метод удалит исходные 3 элемента DOM и добавит 3 новых элемента DOM.

Это говорит о том, что мы могли бы просто предоставить trackByFn, который возвращает постоянное значение без каких-либо неожиданных результатов. Из-за того, что я просмотрел исходный код и поиграл с ним, я не могу понять, в чем проблема (кроме того, что он сбивает с толку других людей, которые смотрят на ваш код).

Это заставляет меня задуматься, почему значение по умолчанию Реализация должна воссоздать все элементы DOM, когда повторное использование старых элементов DOM работает нормально. Я уверен, что есть некоторые случаи, которые я не рассматривал, и я хотел бы услышать их.

DEMO: https://stackblitz.com/edit/angular-anejhw

Это превратилось в немного «веселой» исследовательской задачи, а не однозначного ответа, но, надеюсь, она окажется полезной. Несмотря на то, что я показал, что возвращение константного значения из trackByFn представляется наиболее эффективным вариантом, я все равно буду колебаться при использовании этого подхода в рабочем коде. Даже если теперь это работает для всех случаев, я не удивлюсь, если это будет исправлено в какой-то момент как ошибка.

ngForOf исходный код: https://github.com/angular/angular/blob/master/packages/common/src/directives/ng_for_of.ts

1 голос
/ 02 апреля 2020

TLDR; не возвращайте undefined или какую-либо другую константу в вашем trackBy. Это ничего не значит, и есть лучшие альтернативы, какие бы вам ни понадобились.

Детали реализации ngFor довольно сложны, и я не собираюсь разбирать их ради этого ответа. Кроме того, нам действительно не нужно анализировать каждую деталь. Нам просто нужно понять, какова цель trackBy:

trackBy - это средство для облегчения отслеживания элементов в итерируемом источнике, чтобы (1) оптимизировать производительность и (2) предотвратить уничтожение предметов, которые могли бы остаться в живых .

(1), очевидно: чем меньше манипуляций с DOM, тем выше производительность. (2) не так много: если я хочу анимировать элементы в списке (например, перемещать элементы во время анимации операции сортировки или показывать анимацию :enter или :leave), мне действительно нужно, чтобы экземпляры, компоненты которых были созданы, отслеживались «должным образом» , Если я не настрою trackBy правильно, элементы будут излишне уничтожены и воссозданы, вместо того, чтобы перемещаться правильно.

Чтобы лучше показать это, я настроил пример, чтобы показать точку:

https://stackblitz.com/edit/angular-dnh2ti

Каждый ngFor генерирует AppCounter экземпляров, способных отслеживать совокупное количество вызовов конструктора и отображать значение name. Другими словами, мы можем отследить, воссоздан ли компонент и сброшен ли вход компонента.

Первая кнопка изменяет значение привязки name. Нет ngFor воссоздает компоненты, потому что элементы по умолчанию отслеживаются экземпляром объекта.

Вторая кнопка (Создать новые экземпляры) показывает, что даже если мы полностью изменим экземпляры, восстановление объектов в массиве из с нуля, мы можем сказать компоненту ngFor, чтобы управлять созданием компонента более разумным способом. То есть: Счетчики для комплексных значений - без trackBy воссоздает все компоненты при каждом нажатии кнопки; как и trackByUndefined, тогда как trackByProperty правильно предотвращает разрушение компонента. Это очень важно, если вам нужно анимировать вход и выход или если создание компонента может быть дорогостоящим. Это особенно верно для компонентов элементов с onPush changeDetectionStrategy.

Метод trackByProperty - это один слой:

trackByPropertyName(idx: number, value: any) {
  // use ?. on new TypeScript versions
  return value && value.name;
}

Вторая кнопка также рандомизирует массив randomValues , Это используется, чтобы показать другую альтернативу методу trackByProperty: trackByIndex.

Если вы действительно хотите сохранить все компоненты в том же порядке, в котором они отображаются, и хотите, чтобы обновлялись только данные можно использовать один простой, но эффективный вариант:

trackByIndex(idx: number, _value: any) {
  return idx;
}

Это, очевидно, предотвращает анимацию при сортировке, но сохраняет экземпляры компонентов "живыми", даже если значение полностью изменяется.

Это Ответ уже довольно длинный, почти пост в блоге, но я хочу повторить мысль: используйте trackBy, чтобы сообщить ngFor, какую стратегию использовать для оптимизации создания компонентов. Функция будет, как правило, однострочной, просто возвращая свойство id или индекс строки, поэтому в этом нет никакой дополнительной сложности, но это улучшит производительность и поведение ваших ngFor s и намного более предсказуемо Используйте значение по умолчанию, только если у вас есть простые значения или вы вообще не заботитесь о производительности (но, на самом деле, кто не имеет?).

...