Непонимание обнаружения изменений Angular2 - с поршнем
Я пытаюсь полностью понять обнаружение изменений с помощью Angular2 final.
Это включает в себя:
- Работа со стратегиями обнаружения изменений
- Присоединение и отсоединение детектора изменений от компонента.
Я думал, что у меня уже есть достаточно четкий обзор этих концепций, но чтобы убедиться в правильности своих предположений, я написал небольшой планер для их проверки.
Мое общее понимание о том, где это глобально правильно, но в некоторых ситуациях я немного теряюсь.
Вот плункер: Angular2 площадка обнаружения изменений
Краткое описание плункера:
Довольно просто:
- Один родительский компонент, где вы можете редактировать один атрибут, который будет передан двум дочерним компонентам:
- У ребенка с установленной стратегией обнаружения изменений OnPush
- На ребенка с стратегией обнаружения изменений, установленной по умолчанию
Атрибут parent может быть передан дочерним компонентам одним из следующих способов:
- Изменение всего объекта атрибута и создание нового (кнопка " Изменить объект ") (которая запускает обнаружение изменений в дочернем элементе OnPush)
- Изменение элементов внутри объекта атрибута (кнопка "Изменить содержимое") (которые не вызывают обнаружение изменений в дочернем элементе OnPush)
Для каждого дочернего компонента ChangeDetector может быть присоединен или отсоединен. (кнопки "detach()" и "reattach()")
У дочернего объекта OnPush есть дополнительное внутреннее свойство, которое можно редактировать, и обнаружение изменений может быть применено явным образом (кнопка "detectChanges()")
Вот сценарии, где я получаю поведение, которое не могу объяснить:
Scenario1:
- Детектор изменений отсоединения дочерних и стандартных дочерних объектов OnPush (нажмите " detach () " на обоих компонентах)
- Изменить родительский атрибут имя и фамилия
- Нажмите " Изменить объект ", чтобы передать измененный атрибут дочерним элементам.
Ожидаемое поведение: я ожидаю, что ОБА дети не будут обновлены, потому что у них обоих отключен детектор изменений.
Текущее поведение: дочерний элемент по умолчанию не обновляется, но дочерний элемент OnPush обновляется.. ПОЧЕМУ? Этого не должно быть, потому что его диск отсоединен...
Scenario2:
- Отсоединить CD для компонента OnPush
- Отредактируйте его внутренний ввод значения и нажмите " изменить внутренний": ничего не происходит, потому что CD отсоединен, поэтому изменение не обнаружено... OK
- Нажмите DetectChanges (): изменения обнаружены, и представление обновлено. Все идет нормально.
- Еще раз отредактируйте ввод внутреннего значения и нажмите " изменить внутреннее": еще раз, ничего не происходит, потому что CD отсоединен, поэтому изменение не обнаружено.. OK
- Отредактируйте родительский атрибут имени и фамилии.
- Нажмите " Изменить объект ", чтобы передать измененный атрибут дочерним элементам.
Ожидаемое поведение: дочерние элементы OnPush НЕ ДОЛЖНЫ обновляться во ВСЕХ, еще раз, потому что его CD отсоединен... CD не должен вообще возникать на этом компоненте
Текущее поведение: и значение, и внутренние значения обновляются, к этому компоненту применяются швы, как полный CD.
- В последний раз отредактируйте ввод внутреннего значения и нажмите " изменить внутреннее": изменение обнаружено, а внутреннее значение обновлено...
Ожидаемое поведение: внутреннее значение НЕ должно обновляться, потому что CD все еще отсоединен
Текущее поведение: обнаружено внутреннее изменение значения... ПОЧЕМУ?
Выводы:
В соответствии с этими тестами я заключаю следующее, что кажется мне странным:
- Компонент со стратегией OnPush 'обнаружен измененным', когда их вход изменяется, ДАЖЕ, ЕСЛИ их детектор изменений отключен.
- Компонент со стратегией OnPush получает детектор изменений каждый раз, когда их вход изменяется...
Что вы думаете об этих выводах?
Можете ли вы объяснить это поведение лучше?
Это ошибка или желаемое поведение?
1 ответ
Обновить
Компонент со стратегией OnPush 'обнаружен измененным', когда их вход изменяется, ДАЖЕ, ЕСЛИ их детектор изменений отключен.
Начиная с Angular 4.1.1 (2017-05-04) OnPush
должен уважать detach()
https://github.com/angular/angular/commit/acf83b9
Старая версия
Есть много недокументированных материалов о том, как работает обнаружение изменений.
Мы должны знать о трех основных статусах changeDetection (cdMode
):
1) CheckOnce - 0
CheckedOnce
означает, что после вызова Detechanges режим детектора изменений станетChecked
,
Класс AppView
detectChanges(throwOnChange: boolean): void {
...
this.detectChangesInternal(throwOnChange);
if (this.cdMode === ChangeDetectorStatus.CheckOnce) {
this.cdMode = ChangeDetectorStatus.Checked; // <== this line
}
...
}
2) Проверено - 1
Checked
означает, что детектор изменений должен быть пропущен, пока его режим не изменится наCheckOnce
,
3) Отдельно стоящее - 3
Detached
означает, что поддерево детектора изменений не является частью основного дерева и должно быть пропущено.
Здесь есть места, где Detached
используется
Класс AppView
Пропустить проверку содержимого
detectContentChildrenChanges(throwOnChange: boolean) {
for (var i = 0; i < this.contentChildren.length; ++i) {
var child = this.contentChildren[i];
if (child.cdMode === ChangeDetectorStatus.Detached) continue; // <== this line
child.detectChanges(throwOnChange);
}
}
Пропустить проверку просмотра
detectViewChildrenChanges(throwOnChange: boolean) {
for (var i = 0; i < this.viewChildren.length; ++i) {
var child = this.viewChildren[i];
if (child.cdMode === ChangeDetectorStatus.Detached) continue; // <== this line
child.detectChanges(throwOnChange);
}
}
Пропустить изменение cdMode
в CheckOnce
markPathToRootAsCheckOnce(): void {
let c: AppView<any> = this;
while (isPresent(c) && c.cdMode !== ChangeDetectorStatus.Detached) { // <== this line
if (c.cdMode === ChangeDetectorStatus.Checked) {
c.cdMode = ChangeDetectorStatus.CheckOnce;
}
let parentEl =
c.type === ViewType.COMPONENT ? c.declarationAppElement : c.viewContainerElement;
c = isPresent(parentEl) ? parentEl.parentView : null;
}
}
Замечания: markPathToRootAsCheckOnce
работает во всех обработчиках событий вашего представления:
Так что если установить статус на Detached
тогда ваше мнение не изменится.
Тогда как работает OnPush
стратегия
OnPush
означает, что режим детектора изменений будет установлен наCheckOnce
во время гидратации.
Компилятор / SRC / view_compiler / property_binder.ts
const directiveDetectChangesStmt = isOnPushComp ?
new o.IfStmt(directiveDetectChangesExpr, [compileElement.appElement.prop('componentView')
.callMethod('markAsCheckOnce', [])
.toStmt()]) : directiveDetectChangesExpr.toStmt();
Давайте посмотрим, как это выглядит в вашем примере:
Родительская фабрика (AppComponent)
И снова вернемся к классу AppView:
markAsCheckOnce(): void { this.cdMode = ChangeDetectorStatus.CheckOnce; }
Сценарий 1
1) Детектор изменений отсоединения дочерних и стандартных дочерних элементов OnPush (нажмите "detach()" на обоих компонентах)
OnPush.cdMode - Detached
3) Нажмите "Изменить объект", чтобы передать измененный атрибут дочерним элементам.
AppComponent.detectChanges
||
\/
//if (self._OnPush_35_4.detectChangesInInputProps(self,self._el_35,throwOnChange)) {
// self._appEl_35.componentView.markAsCheckOnce();
//}
OnPush.markAsCheckOnce
||
\/
OnPush.cdMode - CheckOnce
||
\/
OnPush.detectChanges
||
\/
OnPush.cdMode - Checked
Следовательно OnPush.dectectChanges
стреляет.
Вот вывод:
Компонент с
OnPush
стратегия "изменен обнаружен", когда их вход изменяется, даже если их детектор изменений отключен. Более того, он меняет статус просмотра наCheckOnce
,
Scenario2
1) Отключите CD для компонента OnPush
OnPush.cdMode - Detached
6) Нажмите "Изменить объект", чтобы передать измененный атрибут дочерним элементам.
See 3) from scenario 1 => OnPush.cdMode - Checked
7) В последний раз отредактируйте ввод внутреннего значения и нажмите "изменить внутреннее": изменение обнаружено, а внутреннее значение обновлено...
Как я упоминал выше, все обработчики событий включают markPathToRootAsCheckOnce
, Так:
markPathToRootAsCheckOnce
||
\/
OnPush.cdMode - CheckOnce
||
\/
OnPush.detectChanges
||
\/
OnPush.cdMode - Checked
Как видите, стратегия OnPush и ChangeDetector управляют одним свойством - cdMode
Компонент со стратегией OnPush получает детектор изменений каждый раз, когда их вход изменяется...
В заключение хочу сказать, что кажется, ты прав.