问题

我正在尝试使用Angular2最终完全理解更改检测.

这包括:

  • 处理变化检测战略
  • 从组件中附加和分离变更检测器。

我以为我已经对这些概念有了非常清楚的概述,但为了确保我的假设在哪里,我写了一个小的plunkr来测试它们.

我的普遍理解是,在全球范围内,但在某些情况下,我有点迷失了。


这是柱塞克: Angular2更改检测游乐场

对柱塞的快速解释:

非常简单:

  • 一个父组件,您可以编辑一个属性,该属性将传递给两个子组件:
  • 关于将更改检测策略设置为OnPush的儿童
  • 关于将更改检测策略设置为默认的孩子

父属性可以通过以下任何一个传递给子组件:

  • 更改整个属性对象,并创建一个新的属性对象(“更改obj”按钮)(在OnPush子项上触发更改检测)
  • 更改属性对象中的成员(“更改内容”按钮)(不会在OnPush子项上触发更改检测)

对于每个子组件,ChangeDetector可以附加或分离. (“detach()”和“reattach()”按钮)

OnPush子有一个可以编辑的额外内部属性, 并且可以显式应用更改检测(“侦测更改()”按钮)


以下是我得到行为的场景,我无法解释:

风景1:

  1. Detach Change of OnPush Children和Default Children(在两个组件上单击“detach()”)
  2. 编辑父属性firstname和lastname
  3. 单击“Change obj”将修改后的属性传递给子项

预期行为: 我希望BOTH儿童不会更新,因为他们都有自己的变更检测器分离。

当前行为: 默认子项没有更新,但更新了OnPush子项.. WHY? 它不应该因为它的CD是分离的...

场景2:

  1. javascript – 为OnPush组件分离CD
  2. 编辑其内部值输入并单击内部更改:没有任何事情发生,因为CD是分离的,所以没有检测到更改...
  3. click tectChanges():检测到更改并更新视图.到目前为止还很好.
  4. 再次,编辑内部值输入并单击内部更改:再次,没有发生任何事情,因为CD是分离的,所以没有检测到更改..
  5. 编辑父属性firstname和lastname.
  6. 单击“Change obj”将修改后的属性传递给子项

预期行为: OnPush儿童不应该在ALL上更新,因为它的CD是分离的....CD不应该在这个组件上发生

当前行为: 这个值和内部值都被更新,像完整的CD一样的接缝被应用到这个组件中。

  1. 最后一次,编辑内部值输入并单击内部更改:检测更改,更新内部值...

预期行为: 内部价值不应更新,因为CD仍然分离

当前行为: 检测到内部值变化... WHY?


结论:

根据这些测试,我得出以下结论,这对我来说很奇怪:

  • 使用 OnPush 策略的组件在输入更改时被“更改检测”,EVEN IF 将其更改检测器分离。
  • 使用OnPush策略的组件每次输入更改时都会重新连接他们的更改检测器...

你对这些结论有何看法?

你能更好地解释这种行为吗?

这是一个错误还是想要的行为?

  最佳答案

更新

ios – OnPush战略的组件在输入时被“更改检测到” 变化,EVEN IF 他们的变化检测器被分离。

因为Angular 4.1.1(2017-05-04)OnPush应该尊重detach()

https://github.com/angular/angular/commit/acf83b9

旧版本

关于变更检测是如何工作的,有许多无证件的东西。

我们应该知道三个主要的changeDelections状态(cdMode):

(1) 一次检查 -- -- 0

CheckedOnce意味着在调用dectChange之后, 变更检测器将成为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);
  }
}
 

跳过更改cdModeCheckOnce

 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在视图的所有事件处理程序中运行:

enter image description here

因此,如果将状态设置为 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)

Enter image description here

并再次返回appview类:

 markAsCheckOnce(): void { this.cdMode = ChangeDetectorStatus.CheckOnce; }
 

设想1

1) OnPush Childron和Default Childron的Detach Change检测器(在两个组件上单击“detach()”)

 OnPush.cdMode - Detached
 

3)单击“Change obj”将修改后的属性传递给子项

 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正在射击.

这是结论:

javascript – 使用OnPush策略的组件在它们时被“更改检测到” 输入变化,EVEN IF 它们的变化检测器被分离。 它将视图的状态更改为CheckOnce.

设想2

(1) OnPush组件的Detach CD

 OnPush.cdMode - Detached
 

6)单击“更改obj”将修改后的属性传递给 儿童

 See 3) from scenario 1 => OnPush.cdMode - Checked
 

7)最后一次,编辑内部值输入并单击更改 内部:检测变更,更新内部值...

正如我上面提到的,所有事件处理程序都包括markPathToRootAsCheckOnce.所以:

 markPathToRootAsCheckOnce
        ||
        \/
OnPush.cdMode - CheckOnce
        ||
        \/
OnPush.detectChanges
        ||
        \/
OnPush.cdMode - Checked
 

正如您所看到的 OnPush 策略和 ChangeDetector 管理一个属性 – cdMode

javascript – 使用OnPush策略的组件重新附加他们的变更检测器 每次他们的输入发生变化...

最后,我想说,你似乎是对的。

  相同标签的其他问题

angularplunkerangular2-changedetection