Стратегии обнаружения изменений в Angular  -  «onPush» и «Default»

В Angular есть две стратегии обнаружения изменений  —  «Default» и «onPush». Рассмотрим преимущества и недостатки этих стратегий.

Что такое «обнаружение изменений» в Angular?

Механизм обнаружения изменений в Angular отвечает за придание компоненту динамичности. Во время цикла обнаружения изменений Angular ищет все привязки, повторно выполняет всё выражение, сравнивает его с предыдущими значениями и, если изменение обнаружено, распространяет его на элементы DOM.

Обнаружение изменений в Angular выполняется, когда:

  1. Имеют место обновления переменной состояния в Angular.
  2. Внутри компонента Angular вызываются события.
  3. Обновляются значения @Input для компонентов.

Что представляет собой стратегия обнаружения изменений в Angular?

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

Стратегия обнаружения изменений «Default»

Если стратегия компонента не настраивается, она обозначается как стратегия «Default». Стратегия обнаружения «Default» применяется к компоненту во время его создания. В этой стратегии цикл обнаружения изменений выполняется для каждого отдельного события, происходящего внутри компонента.

  1. Событие нажатия на элементы.
  2. Получение данных через асинхронный вызов.
  3. Запуск setTimeout и setInterval.

Рассмотрим проблемы, связанные с этой стратегией.

Выше приведены два компонента: ParentComponent (родительский) и ChildComponent (дочерний). Родительский компонент содержит свойство counter (счётчик). Оно может обновляться при каждом нажатии пользователя на кнопку. И каждый раз, когда значение счётчика counter обновляется, происходит повторное отображение компонента. В этом компоненте имеется также дочерний компонент. Он не зависит от данных счётчика counter, ведь мы не передаём данные в дочерний компонент ChildComponent через @Input.

@Component({
  selector: "app-root",
  template: `
    <div>
      <h1>Counter Value: {{this.counter}}</h1>
      <input type="button" (click)="this.updateCounter()" value="Update Counter" />
      <child-component></child-component>
      </div>
  `  
})
export class ParentComponent {
  counter = 0;

  updateCounter() {
      this.counter += 1;
  }
}

Посмотрим теперь на реализацию дочернего компонента. ChildComponent  —  это статический компонент, на который не оказывает никакого влияния изменение родительских данных. Даже если обновляется значение счётчика родительского компонента ParentComponent, представление дочернего компонента ChildComponent должно оставаться неизменным. Внутри декоратора @Component приведённого ниже компонента мы не предоставляем никакой стратегии. Здесь по умолчанию настраивается стратегия обнаружения изменений «Default».

@Component({
  selector: "child-component",
  template: `
    <div><h3>{{this.executeFunction()}}</h3></div>
  `
})
export class ChildComponent {
  executeFunction() {
    console.log("App Rerendered")
    return "This is Child Component"
  }
}

Что происходит согласно стратегии обнаружения изменений «Default»: каждый раз, когда в ParentComponent (родительском компоненте) обновляется счётчик, в жизненном цикле ChildComponent также инициируется повторное отображение дочернего компонента. Причём последний отображается повторно даже тогда, когда изменение счётчика не влияет на представление дочернего компонента ChildComponent. Дочерний компонент ChildComponent не зависит от данных родительского компонента ParentComponent.

В приведённом выше коде каждый раз, когда отображается дочерний компонент, в журнале консоли регистрируется сообщение App Rerendered (Приложение отображено повторно). То есть появление такого сообщения в окне консоли означает, что дочерний компонент тоже отображается. Можете проверить это поведение в случае стратегии обнаружения изменений «Default» в виртуальном редакторе:

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

Стратегия обнаружения изменений «onPush»

Для решения обозначенной проблемы применяется стратегия обнаружения изменений «onPush». Согласно этой стратегии, дочерний компонент ChildComponent не всегда проверяется на изменение данных. Такая проверка не должна проводиться, если родительский элемент обновляет значения, которые не передаются дочернему компоненту в свойствах @Input.

Преимущества стратегии обнаружения изменений «onPush»

  • Нет ненужной проверки на изменение данных в дочерних компонентах.
  • Более быстрое повторное отображение компонентов.
@Component({
  selector: "child-component",
  template: `
    <div>
      <h2>{{ this.executeFunction() }}</h2>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent {
  executeFunction() {
    console.log("App Rerendered");
    return "This is Child Component";
  }
}

В коде мы обновили стратегию обнаружения изменений для дочернего компонента. Согласно этой стратегии «onPush», компонент не будет обновляться/повторно отображаться при обновлении свойства родительского компонента. В приведённом выше коде никакого обновляющегося свойства @Input нет, поэтому повторного отображения компонента не произойдёт. И это будет более эффективно.

Будьте осторожны с мутацией объектов

Объекты, полученные в качестве свойств @Input, не должны мутировать. Когда объекты @Input получаются из родительского компонента, это происходит по ссылке. Если исходный объект мутирует из родительского элемента, то ссылка не обновляется в дочернем компоненте. Поэтому компонент @Input не получает новую ссылку и не будет обновляться, ведь объекты в свойстве @Input остаются прежними (ссылка).

В этом случае нет механизма, с помощью которого можно было бы уведомить дочерний компонент об обновлении одного из свойств @Input. Нужно создать новый объект с обновлёнными свойствами, чтобы ссылка со свойством @Input обновлялась, а дочерний компонент был уведомлен об обновлении компонента. Вследствие чего будет происходить повторное отображение компонента.

Реализацию стратегии «onPush» смотрите в приведённом выше коде.

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Mayank Gupta: Angular Change Detection Strategy — onPush and Default Strategy