Ответ 1
Хорошо, так как это заняло у меня целый вечер, чтобы понять, что я сделал резюме, чтобы все уладить у меня в голове, и это может помочь будущим читателям. Поэтому давайте начнем с очистки некоторых вещей:
Изменения происходят из событий
У компонента могут быть поля. Эти поля меняются только после какого-то события, и только после этого.
Мы можем определить событие как щелчок мыши, ajax-запрос, setTimeout...
Потоки данных сверху вниз
Angular поток данных - это односторонняя улица. Это означает, что данные не передаются от детей к родителям. Только от родителя к детям, например, через тег @Input
. Единственный способ сделать верхний компонент осведомленным о некоторых изменениях в дочернем элементе - через событие. Это подводит нас к:
Обнаружение изменения триггера события
Когда происходит событие, среда angular проверяет каждый компонент сверху вниз, чтобы узнать, были ли они изменены. Если какой-либо из них изменился, он соответственно обновляет представление.
Angular проверяет все компоненты после того, как событие было запущено. Скажем, у вас есть событие click на компоненте, который является компонентом на самом низком уровне, то есть у него есть родители, но нет детей. Этот клик может инициировать изменение в родительском компоненте с помощью излучателя события, службы и т.д. angular не знает, изменятся ли родители или нет. Вот почему angular проверяет все компоненты после того, как событие было запущено по умолчанию.
Чтобы узнать, изменили ли они 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 не изменился.
@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,
}
После щелчка имя по-прежнему остается в представлении, но не в самом компоненте
Событие, запущенное внутри компонента, вызовет обнаружение изменений.
Здесь мы приходим к тому, что меня смущает в моем первоначальном вопросе. Компонент ниже отмечен стратегией OnPush, но представление обновляется, когда оно изменяется.
@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)
- | асинхронная труба получила событие
- обнаружение изменений было вызвано "вручную"
независимо от стратегии.
Ссылки
- https://hackernoon.com/everything-you-need-to-know-about-change-detection-in-angular-8006c51d206f
- http://blog.angular-university.io/how-does-angular-2-change-detection-really-work/
- https://angular-2-training-book.rangle.io/handout/change-detection/change_detector_classes.html
- https://www.youtube.com/watch?v=X0DLP_rktsc