Как оптимизировать производительность с OnPush стратегией?angular-46

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

1. Основные принципы OnPush

Стратегия OnPush работает по следующим правилам:

  • Компонент проверяется только когда:
    • Изменяются входные свойства (@Input)
    • Происходит событие в самом компоненте
    • Вызывается markForCheck()
    • Async pipe получает новое значение

2. Ключевые техники оптимизации

2.1. Иммутабельные структуры данных

// Плохо - мутация объекта
this.user.name = 'New Name';

// Хорошо - создание нового объекта
this.user = {...this.user, name: 'New Name'};

Почему важно: OnPush проверяет ссылки, а не содержимое объектов.

2.2. Использование async pipe

// В компоненте
data$ = this.service.data$;

// В шаблоне
{{ data$ | async }}

Преимущества:

  • Автоматическая отписка
  • Автоматический вызов markForCheck()
  • Чистый декларативный подход

2.3. Чистые пайпы

@Pipe({
  name: 'fullName',
  pure: true // true по умолчанию
})
export class FullNamePipe implements PipeTransform {
  transform(user: User): string {
    return `${user.firstName} ${user.lastName}`;
  }
}

Важно: Pure pipes кэшируют результаты при тех же входных параметрах.

2.4. TrackBy для ngFor

// В компоненте
trackByFn(index: number, item: Item): number {
  return item.id; // Уникальный идентификатор
}

// В шаблоне
<div *ngFor="let item of items; trackBy: trackByFn">

Эффект: Angular будет перерисовывать только измененные элементы.

2.5. Ленивая загрузка

const routes: Routes = [
  {
    path: 'feature',
    loadChildren: () => import('./feature/feature.module').then(m => m.FeatureModule)
  }
];

Результат: Загружаются только необходимые компоненты.

3. Продвинутые техники

3.1. Ручное управление ChangeDetectorRef

constructor(private cdr: ChangeDetectorRef) {}

updateData() {
  this.dataService.getData().subscribe(data => {
    this.data = data;
    this.cdr.markForCheck(); // Явно помечаем для проверки
  });
}

3.2. Отключение Zone.js

platformBrowser()
  .bootstrapModule(AppModule, {ngZone: 'noop'});

Когда использовать: Для максимальной производительности в сложных приложениях.

3.3. Компоненты без представления

@Component({
  selector: 'app-data-provider',
  template: '',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class DataProviderComponent {
  @Input() data: any;
}

Применение: Для изоляции тяжелых вычислений.

4. Антипаттерны

  1. Мутация данных:

    // Плохо
    this.items.push(newItem);
    // Хорошо
    this.items = [...this.items, newItem];
    
  2. Сложные вычисления в шаблоне:

    <!-- Плохо -->
    {{ calculateComplexValue() }}
    <!-- Лучше -->
    {{ preCalculatedValue }}
    
  3. Избыточные проверки:

    // Избегайте лишних detectChanges()
    this.cdr.detectChanges();
    

5. Инструменты анализа

  1. Angular DevTools:

    • Профилирование изменений
    • Визуализация дерева компонентов
  2. Chrome Performance Tab:

    • Анализ времени рендеринга
    • Выявление узких мест

Резюмируем

Оптимизация с OnPush:

  1. Иммутабельность — основа работы OnPush
  2. Async pipe — предпочтительный способ подписки
  3. TrackBy — обязательно для списков
  4. Чистые пайпы — кэширование вычислений
  5. Ленивая загрузка — уменьшение начальной загрузки

Пример идеального компонента:

@Component({
  selector: 'app-optimized',
  template: `
    <div *ngFor="let item of items$ | async; trackBy: trackByFn">
      {{ item.name | uppercase }}
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class OptimizedComponent {
  items$ = this.service.getItems();

  trackByFn = (index: number, item: Item) => item.id;

  constructor(private service: DataService) {}
}

Итоговый результат: При правильном применении OnPush можно добиться 2-10x улучшения производительности в крупных приложениях.