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 detection | Angular 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 уменьшает начальный бандл, что напрямую улучшает производительность загрузки