Angular

Производительность и change detection

Список из тысячи строк подтормаживает при каждом клике где угодно на странице. Причина не в самом списке, а в том, что Angular перепроверяет всё дерево компонентов на любое событие. Производительность в Angular 21 - это управление двумя вещами: как часто запускается change detection и сколько кода грузится на старте. Zoneless-режим, OnPush, сигналы и track сужают перепроверки до того, что реально изменилось.

  • Таблицы и списки на тысячи строк: track в @for предотвращает пересоздание узлов DOM
  • Дашборды реального времени: сигналы перерисовывают только изменившийся виджет
  • Zoneless-приложения: заметно быстрее начальный рендер по замерам команды Angular
  • Бюджеты бандла: сборка падает, если размер превысил порог, не давая регрессу попасть в прод
  • Профилирование: Angular DevTools показывает, какие компоненты проверяются и сколько это стоит

Предварительные знания

  • Change detection и zoneless-режим: как Angular решает, что перепроверять
  • Сигналы: signal, computed, их роль в реактивности
  • Базовое понимание сборки и размера бандла

Путь к точечным перепроверкам

Исторически Angular полагался на Zone.js: библиотека патчила браузерные API и запускала change detection на любое асинхронное событие, проверяя всё дерево. Это работало, но было избыточно. Стратегия OnPush (с Angular 2) позволяла пропускать поддеревья. Сигналы (v16+) дали реактивность с точностью до значения. Angular 18 принёс экспериментальный zoneless, а к Angular 20-21 zoneless стал способом по умолчанию для новых приложений. Команда сообщает о выигрыше в начальном рендере без Zone.js.

OnPush, сигналы и сужение перепроверок

В классической стратегии Angular проверяет компонент на каждом проходе change detection (в v22 новые компоненты по умолчанию на OnPush). Стратегия OnPush меняет это: компонент проверяется только когда меняется ссылка на входной property, внутри него происходит событие, или меняется прочитанный в шаблоне сигнал. Это отсекает лишние проверки целых поддеревьев.

С OnPush и сигналами Angular точно знает, что перерисовывать: изменение сигнала count помечает только этот компонент как требующий проверки. Соседние поддеревья не трогаются. Это и есть точечная реактивность вместо обхода всего дерева.

Сигналы делают OnPush проще и надёжнее. Раньше с OnPush приходилось вручную вызывать markForCheck при мутациях, а с сигналами Angular сам отслеживает зависимости шаблона и помечает компонент при их изменении.

Что заставляет компонент с OnPush пройти change detection?

Zoneless и track в @for

Zoneless-режим убирает Zone.js: change detection больше не запускается на каждый таймер или промис автоматически. Вместо этого проверку инициируют сигналы, события шаблона и явные уведомления. Меньше лишних проходов, меньше работы на старте - отсюда сообщаемый выигрыш в начальном рендере.

В блоке @for выражение track даёт Angular стабильный идентификатор каждого элемента. Без него при изменении массива Angular не может понять, какие узлы переиспользовать, и пересоздаёт DOM. С правильным track узлы существующих элементов остаются на месте, меняется только разница.

track по $index в изменяемом списке - частая ошибка: при вставке или удалении в середину индексы сдвигаются, и Angular считает изменившимися все элементы после точки вставки. Для стабильности нужен ключ, привязанный к данным, например user.id.

Почему track по user.id предпочтительнее track по $index в изменяемом списке?

Профилирование и бюджеты бандла

Оптимизировать стоит измеренное, а не предполагаемое. Angular DevTools во вкладке Profiler записывает циклы change detection и показывает, какие компоненты проверяются и сколько времени это занимает. Горячий компонент в профиле - кандидат на OnPush или сигналы.

Вторая часть производительности - размер бандла. Бюджеты в angular.json задают пороги: при превышении сборка выдаёт предупреждение или падает с ошибкой. Это не даёт незаметному росту бандла попасть в прод и держит начальную загрузку под контролем.

ПриёмЧто снижаетИнструмент
OnPush и сигналыЧастоту и охват change detectionAngular DevTools Profiler
ZonelessЛишние проходы проверкиprovideZonelessChangeDetection
track в @forПересоздание узлов DOMПрофиль рендера списка
@defer и lazy routesРазмер начального бандлаБюджеты, source-map-explorer

Бюджет типа initial следит за начальным бандлом - тем, что грузится до первого рендера. Перенос тяжёлых частей в @defer или ленивые маршруты уводит их из этого бюджета, потому что они грузятся отдельными чанками позже.

Для чего служит бюджет бандла в angular.json?

Связь с другими темами

Производительность стоит на change detection и пользуется ленивой загрузкой:

  • Change detection и zoneless — Фундамент: оптимизации управляют тем, как и когда запускается проверка
  • @defer — Уменьшает начальный бандл, снимая часть кода со старта

Итог

  • Производительность - это контроль частоты change detection и объёма кода на старте
  • OnPush ограничивает проверку компонента изменением входов, событием или сигналом внутри него
  • Zoneless убирает Zone.js: change detection запускают сигналы и явные события, команда сообщает о выигрыше в начальном рендере
  • track в @for даёт Angular стабильный ключ, чтобы переиспользовать узлы DOM вместо пересоздания
  • Бюджеты бандла в angular.json валят сборку при превышении размера, а Angular DevTools показывает горячие компоненты

Связанные уроки

  • ng-21-change-detection-zoneless — Оптимизация change detection строится на понимании зон и zoneless-режима
  • ng-33-defer — @defer уменьшает начальный бандл, что напрямую улучшает производительность загрузки
Производительность и change detection

0

1

Войти