Angular2变化检测误解 - 使用plunker

时间:2016-10-30 17:56:02

标签: angular plunker angular2-changedetection

我试图用Angular2 final完全理解变化检测。

这包括:

  • 处理变更检测策略
  • 从组件上安装和拆卸更换检测器。

我认为我已经清楚地了解了这些概念,但为了确保我的假设正确,我写了一个小傻瓜来测试它们。

我对全球正确的一般理解,但在某些情况下,我有点迷失。

以下是plunker:Angular2 Change detection playground

对弹药的快速解释:

非常简单:

  • 一个父组件,您可以在其中编辑一个将传递给两个子组件的属性:
  • 将更改检测策略设置为OnPush的儿童
  • 将具有更改检测策略的子项设置为“默认”

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

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

对于每个子组件,可以附加或分离ChangeDetector。 (" detach()" " reattach()" 按钮)

OnPush child有一个可以编辑的附加内部属性, 并且可以明确应用更改检测(" detectChanges()" 按钮)

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

Scenario1:

  1. 分离OnPush儿童和默认儿童的更改检测器(单击" 分离()"在两个组件上)
  2. 编辑父属性firstname和lastname
  3. 点击" 更改对象"将修改后的属性传递给子项
  4. 预期行为 我希望 BOTH 的孩子不会被更新,因为他们都更换了变量探测器。

    当前行为: 默认子项未更新,但OnPush子项已更新.. 为什么? 它不应该因为它的CD分离......

    Scenario2:

    1. 为OnPush组件分离CD
    2. 修改内部值输入并点击更改内部:没有任何结果,因为CD已分离,因此未检测到更改...确定
    3. 点击 detectChanges():检测到更改并更新视图。到目前为止一切顺利。
    4. 再次编辑内部值输入,然后点击更改内部:再次,没有任何反应,因为CD已分离,因此未检测到更改..确定< / LI>
    5. 编辑父属性firstname和lastname。
    6. 点击&#34; 更改对象&#34;将修改后的属性传递给子项
    7. 预期行为 OnPush的孩子不应该全部更新,因为它的CD是分离的...... CD不应该在这个组件上发生

      当前行为: 值和内部值都会更新,像完整CD这样的接缝会应用于此组件。

      1. 最后一次,修改内部值输入,然后点击更改内部:检测到更改,并更新内部值...
      2. 预期行为 不应更新内部值,因为CD仍然已分离

        当前行为: 检测到内部价值变化...... 为什么?

        结论:

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

        • 使用OnPush策略的组件在输入更改时&n 39;检测到更改 即使他们的更改检测器已分离。
        • 具有OnPush策略的组件每次输入更改时都会重新连接其变化检测器...

        您如何看待这些结论?

        你能以更好的方式解释这种行为吗?

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

1 个答案:

答案 0 :(得分:13)

更新

  

具有OnPush策略的组件被检测到已更改&#39;什么时候输入   变化,即使它们的变化探测器已分离。

由于 Angular 4.1.1 (2017-05-04)OnPush应尊重detach()

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

旧版

关于变更检测的工作方式,有很多未记载的内容。

我们应该了解三个主要的 changeDetection状态cdMode):

1) CheckOnce - 0

  

CheckedOnce表示在调用detectChanges后的模式   变化检测器将变为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正在您视图的所有事件处理程序中运行:

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 Children和Default Children的更改检测器(单击&#34;分离()&#34;在两个组件上)

OnPush.cdMode - Detached
  

3)点击&#34;更改obj&#34;将修改后的属性传递给子项

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策略的组件被检测到已更改&#39;当他们的   输入更改,即使它们的变化检测器已分离。的此外   它会将观看次结状态更改为CheckOnce

<强> Scenario2

  

1)为OnPush组件分离CD

OnPush.cdMode - Detached
  

6)点击&#34;更改obj&#34;将修改后的属性传递给   儿童

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

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

如上所述,所有事件处理程序都包含markPathToRootAsCheckOnce。所以:

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

正如您所见,OnPush策略和ChangeDetector管理一个属性 - cdMode

  

使用OnPush策略的组件重新连接其变化检测器   每次他们的输入都改变了......

总之,我想说,你似乎是对的。