angular 2 обнаружение изменений и ChangeDetectionStrategy.OnPush

Я пытаюсь понять ChangeDetectionStrategy.OnPush механизм.

из моих показаний я понял, что обнаружение изменений работает путем сравнения старого значения с новым значением. Это сравнение возвращает false, если ссылка на объект не изменился.

однако, похоже,есть определенные сценарии, где это "правило" обходится. Не могли бы вы объяснить как это все работает ?

4 ответов


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

изменения от события

компонент может иметь поля. Эти поля меняются только после какого-то события и только после этого.

мы можем определить событие как щелчок мыши, запрос ajax, setTimeout...

потоки данных сверху дно

угловой поток данных-улица с односторонним движением. Это означает, что данные не передаются от детей к родителям. Только от родителя к детям, например, через @Input тег. Единственный способ сделать верхний компонент осведомленным о некоторых изменениях в ребенке - через событие. Что приводит нас к:

обнаружение изменения триггера события

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

Angular проверяет все компоненты после того, как событие было запущено. Скажем, у вас есть событие click на компонент, компонент на самом низком уровне, то есть родители, но нет детей. Этот щелчок может вызвать изменение родительского компонента с помощью эмиттера событий, службы и т. д.. Угловой не знает, изменятся родители или нет. Именно поэтому Angular проверяет все компоненты после события был уволен по умолчанию.

чтобы узнать, изменили ли они угол, используйте ChangeDetector класса.

Детектор Изменения

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

например, если у нас есть ParentComponent:

@Component({
  selector: 'comp-parent',
  template:'<comp-child [name]="name"></comp-child>'
})
class ParentComponent{
  name:string;
} 

мы будем иметь детектор изменения, прикрепленный к ParentComponent это выглядит так:

class ParentComponentChangeDetector{
    oldName:string; // saves the old state of the component.

    isChanged(newName){
      if(this.oldName !== newName)
          return true;
      else
          return false;
    }
}

изменение свойств объекта

как вы могли заметить, метод isChanged вернет false, если вы измените свойство объекта. Действительно

let prop = {name:"cat"};
let oldProp = prop;
//change prop
prop.name = "dog";
oldProp === prop; //true

С тех пор, когда свойство объекта может изменяться без возврата true в changeDetector isChanged(), angular предположит, что каждый компонент ниже также мог измениться. Таким образом, он будет просто проверять обнаружение изменений во всех компонентах.

пример: у нас есть компонент с компонент. Хотя обнаружение изменений вернет false для родительского компонента, представление ребенка должно быть очень хорошо обновлено.

@Component({
  selector: 'parent-comp',
  template: `
    <div class="orange" (click)="person.name='frank'">
      <sub-comp [person]="person"></sub-comp>
    </div>
  `
})
export class ParentComponent {
  person:Person = { name: "thierry" };     
}

// sub component
@Component({
  selector: 'sub-comp',
  template: `
    <div>
      {{person.name}}
    </div>
})
export class SubComponent{
  @Input("person") 
  person:Person;
}

вот почему поведение по умолчанию-проверить все компоненты. Потому что, хотя субкомпонент не может измениться, если его вход не изменился, angular не знает наверняка, что его вход не изменился действительно изменен. Объект, переданный ему, может быть одинаковым, но он может иметь разные свойства.

стратегия OnPush

когда компонент помечен changeDetection: ChangeDetectionStrategy.OnPush, angular будет считать, что входной объект не изменился, если ссылка на объект не изменилась. Это означает, что изменение свойства не вызовет обнаружение изменений. Таким образом будут синхронизированы с моделью.

пример

этот пример классный, потому что он показывает это в действии. У вас есть родительский компонент, который при нажатии на свойства имени входного объекта изменяется. Если вы проверите click() метод внутри родительского компонента вы заметите, что он выводит свойство дочернего компонента в консоли. Это свойство изменилось..Но вы не можете видеть это визуально. Это потому, что вид не был обновлен. Из-за стратегии OnPush процесс обнаружения изменений не произошел, потому что объект ref не изменение.

Plnkr

@Component({
  selector: 'my-app',
  template: `
    <div class="orange" (click)="click()">
      <sub-comp [person]="person" #sub></sub-comp>
    </div>
  `
})
export class App {
  person:Person = { name: "thierry" };
  @ViewChild("sub") sub;

  click(){
    this.person.name = "Jean";
    console.log(this.sub.person);
  }
}

// sub component
@Component({
  selector: 'sub-comp',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div>
      {{person.name}}
    </div>
  `
})
export class SubComponent{
  @Input("person") 
  person:Person;
}

export interface Person{
  name:string,
}

после щелчка имя по-прежнему thierry в представлении, но не в самом компоненте


событие, запущенное внутри компонента, вызовет обнаружение изменений.

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

Plnkr

@Component({
  selector: 'my-app',
  template: `
    <div class="orange" >
      <sub-comp ></sub-comp>
    </div>
  `,
  styles:[`
    .orange{ background:orange; width:250px; height:250px;}
  `]
})
export class App {
  person:Person = { name: "thierry" };      
  click(){
    this.person.name = "Jean";
    console.log(this.sub.person);
  }

}

// sub component
@Component({
  selector: 'sub-comp',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="grey" (click)="click()">
      {{person.name}}
    </div>
  `,
  styles:[`
    .grey{ background:#ccc; width:100px; height:100px;}
  `]
})
export class SubComponent{
  @Input()
  person:Person = { name:"jhon" };
  click(){
    this.person.name = "mich";
  }
}

Итак, здесь мы видим, что ввод объекта не изменил ссылку, и мы используем стратегию OnPush. Что может привести нас к мысли, что он не будет обновлен. Фактически он обновляется.

как сказал Гюнтер в своем ответе, это потому, что с помощью стратегии OnPush обнаружение изменений происходит для компонента, если:

  • получено связанное событие (нажмите) на компоненте себя.
  • обновлен @Input () (как в ref obj изменен)
  • | асинхронный канал получил событие
  • обнаружение изменений было вызвано "вручную"

независимо от стратегия.

ссылки


*ngFor это собственное обнаружение изменений. Каждый раз, когда выполняется обнаружение изменений,NgFor получает ngDoCheck() метод называется и есть NgFor проверяет, изменилось ли содержимое массива.

в вашем случае изменений нет, потому что конструктор выполняется до того, как Angular начнет отображать представление.
Если бы вы, например, добавили кнопку

<button (click)="persons.push({name: 'dynamically added', id: persons.length})">add</button>

тогда щелчок фактически вызовет изменение, которое ngFor должен распознавать.

С ChangeDetectionStrategy.OnPush обнаружение изменений в вашем компоненте будет выполняться, потому что с OnPush обнаружение изменений выполняется, когда

  • получено связанное событие (click)
  • an @Input() был обновлен с помощью обнаружения изменений
  • | async труба получила событие
  • обнаружение изменений было вызвано "вручную"

предупреждения Application.tick попробуйте отсоединить changeDetector:

constructor(private cd: ChangeDetectorRef) {

ngAfterViewInit() {
  this.cd.detach();
}

Plunker


в angular мы высоко используем структуру родитель-потомок. Там мы передаем данные из родительской формы в дочернюю, используя @Inputs.

там, если изменение происходит на любом предке ребенка, обнаружение изменений произойдет в форме дерева компонентов этого предка.

но в большинстве ситуаций нам нужно будет обновить представление ребенка (обнаружение изменения вызова) только тогда, когда это изменение входных данных. Для этого мы можем использовать OnPush ChangeDetectionStrategy и измените входные сигналы (используя immutables) по мере необходимости. ссылке