Angular2变化检测误解 - 用plunker

Cle*_*ent 8 plunker angular2-changedetection angular

我正在尝试用Angular2 final完全理解变化检测.

这包括:

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

我以为我已经对这些概念进行了非常明确的概述,但为了确保我的假设正确,我写了一个小傻瓜来测试它们.

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


这是plunker:Angular2变化检测操场

对plunker的快速解释:

很简单:

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

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

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

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

OnPush子项具有可以编辑的附加内部属性,并且可以显式应用更改检测("detectChanges()"按钮)


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

Scenario1:

  1. 分离OnPush Children和Default Children的更改检测器(单击两个组件上的" detach() ")
  2. 编辑父属性firstname和lastname
  3. 单击" 更改obj "将修改后的属性传递给子项

预期的行为: 我希望两个孩子都不会被更新,因为他们都有自己的变化探测器.

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

Scenario2:

  1. 为OnPush组件分离CD
  2. 编辑其内部值输入并单击更改内部:没有任何反应,因为CD已分离,因此未检测到更改...确定
  3. 单击detectChanges():检测到更改并更新视图.到现在为止还挺好.
  4. 再一次,编辑内部值输入并单击更改内部:再次,没有任何事情发生,因为CD已分离,因此未检测到更改..确定
  5. 编辑父属性firstname和lastname.
  6. 单击" 更改obj "将修改后的属性传递给子项

预期的行为: OnPush的孩子不应该全部更新,再次因为它的CD被分离...... CD不应该在这个组件上发生

当前行为: 更新值和内部值,将完整CD等接缝应用于此组件.

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

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

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


结论:

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

  • 具有OnPush策略的组件在其输入发生变化时会"检测到已更改",即使它们的变化检测器已分离.
  • 具有OnPush策略的组件每次输入更改时都会重新连接其变化检测器...

您如何看待这些结论?

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

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

yur*_*zui 13

更新

具有OnPush策略的组件在其输入发生变化时会"检测到已更改",即使它们的变化检测器已分离.

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
  }
  ...
}
Run Code Online (Sandbox Code Playgroud)

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

跳过视图检查

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

跳过更改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;
  }
}
Run Code Online (Sandbox Code Playgroud)

注意: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();
Run Code Online (Sandbox Code Playgroud)

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; }
Run Code Online (Sandbox Code Playgroud)

场景1

1)分离OnPush Children和Default Children的Change变换器(点击两个组件上的"detach()")

OnPush.cdMode - Detached
Run Code Online (Sandbox Code Playgroud)

3)单击"更改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
Run Code Online (Sandbox Code Playgroud)

因此OnPush.dectectChanges是开火.

这是结论:

具有OnPush策略的组件在其输入发生变化时会"检测到已更改",即使它们的变化检测器已分离.此外,它将视图的状态更改为CheckOnce.

Scenario2

1)为OnPush组件分离CD

OnPush.cdMode - Detached
Run Code Online (Sandbox Code Playgroud)

6)单击"更改obj"将修改后的属性传递给子项

See 3) from scenario 1 => OnPush.cdMode - Checked
Run Code Online (Sandbox Code Playgroud)

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

如上所述,所有事件处理程序包括markPathToRootAsCheckOnce.所以:

markPathToRootAsCheckOnce
        ||
        \/
OnPush.cdMode - CheckOnce
        ||
        \/
OnPush.detectChanges
        ||
        \/
OnPush.cdMode - Checked
Run Code Online (Sandbox Code Playgroud)

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

具有OnPush策略的组件每次输入更改时都会重新连接其变化检测器...

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