Непонимание обнаружения изменений Angular2 - с поршнем

Я пытаюсь полностью понять обнаружение изменений с помощью Angular2 final.

Это включает в себя:

  • Работа со стратегиями обнаружения изменений
  • Присоединение и отсоединение детектора изменений от компонента.

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

Мое общее понимание о том, где это глобально правильно, но в некоторых ситуациях я немного теряюсь.


Вот плункер: Angular2 площадка обнаружения изменений

Краткое описание плункера:

Довольно просто:

  • Один родительский компонент, где вы можете редактировать один атрибут, который будет передан двум дочерним компонентам:
  • У ребенка с установленной стратегией обнаружения изменений OnPush
  • На ребенка с стратегией обнаружения изменений, установленной по умолчанию

Атрибут parent может быть передан дочерним компонентам одним из следующих способов:

  • Изменение всего объекта атрибута и создание нового (кнопка " Изменить объект ") (которая запускает обнаружение изменений в дочернем элементе OnPush)
  • Изменение элементов внутри объекта атрибута (кнопка "Изменить содержимое") (которые не вызывают обнаружение изменений в дочернем элементе OnPush)

Для каждого дочернего компонента ChangeDetector может быть присоединен или отсоединен. (кнопки "detach()" и "reattach()")

У дочернего объекта OnPush есть дополнительное внутреннее свойство, которое можно редактировать, и обнаружение изменений может быть применено явным образом (кнопка "detectChanges()")


Вот сценарии, где я получаю поведение, которое не могу объяснить:

Scenario1:

  1. Детектор изменений отсоединения дочерних и стандартных дочерних объектов OnPush (нажмите " detach () " на обоих компонентах)
  2. Изменить родительский атрибут имя и фамилия
  3. Нажмите " Изменить объект ", чтобы передать измененный атрибут дочерним элементам.

Ожидаемое поведение: я ожидаю, что ОБА дети не будут обновлены, потому что у них обоих отключен детектор изменений.

Текущее поведение: дочерний элемент по умолчанию не обновляется, но дочерний элемент OnPush обновляется.. ПОЧЕМУ? Этого не должно быть, потому что его диск отсоединен...

Scenario2:

  1. Отсоединить CD для компонента OnPush
  2. Отредактируйте его внутренний ввод значения и нажмите " изменить внутренний": ничего не происходит, потому что CD отсоединен, поэтому изменение не обнаружено... OK
  3. Нажмите DetectChanges (): изменения обнаружены, и представление обновлено. Все идет нормально.
  4. Еще раз отредактируйте ввод внутреннего значения и нажмите " изменить внутреннее": еще раз, ничего не происходит, потому что CD отсоединен, поэтому изменение не обнаружено.. OK
  5. Отредактируйте родительский атрибут имени и фамилии.
  6. Нажмите " Изменить объект ", чтобы передать измененный атрибут дочерним элементам.

Ожидаемое поведение: дочерние элементы OnPush НЕ ДОЛЖНЫ обновляться во ВСЕХ, еще раз, потому что его CD отсоединен... CD не должен вообще возникать на этом компоненте

Текущее поведение: и значение, и внутренние значения обновляются, к этому компоненту применяются швы, как полный CD.

  1. В последний раз отредактируйте ввод внутреннего значения и нажмите " изменить внутреннее": изменение обнаружено, а внутреннее значение обновлено...

Ожидаемое поведение: внутреннее значение НЕ должно обновляться, потому что 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();

https://github.com/angular/angular/blob/2.1.2/modules/%40angular/compiler/src/view_compiler/property_binder.ts#L193-L197

Давайте посмотрим, как это выглядит в вашем примере:

Родительская фабрика (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 получает детектор изменений каждый раз, когда их вход изменяется...

В заключение хочу сказать, что кажется, ты прав.

Другие вопросы по тегам