Angular2 变化检测误解 - With plunker
Angular2 change detection misunderstanding - With plunker
我正在尝试完全理解 Angular2 final 的变化检测。
这包括:
- 处理变化检测策略
- 在组件上附加和分离变化检测器。
我认为我已经对这些概念有了一个非常清晰的概述,但为了确保我的假设正确,我写了一个小插件来测试它们。
我对全局的一般理解是正确的,但在某些情况下,我有点迷茫。
这里是 plunker: Angular2 Change detection playground
plunker的快速解释:
很简单:
- 一个 parent 组件,您可以在其中编辑一个属性,该属性将传递给两个 children 组件:
- 开启 child,更改检测策略设置为 OnPush
- 开启 child,更改检测策略设置为默认值
parent 属性可以通过以下任一方式传递给 children 组件:
- 更改整个属性 object,并创建一个新属性(“Change obj”按钮)(在 OnPush child 上触发更改检测)
- 更改属性 object 中的成员("Change content" 按钮)(不会触发 OnPush child 上的更改检测)
对于每个 child 组件,可以附加或分离 ChangeDetector。 ("detach()" 和 "reattach()" 按钮)
OnPush child 有一个额外的内部 属性 可以编辑,
并且可以显式应用更改检测("detectChanges()" 按钮)
以下是我无法解释的行为的场景:
场景 1:
- 分离 OnPush Children 和默认 Children 的更改检测器(在两个组件上单击“detach()”)
- 编辑 parent 属性名字和姓氏
- 点击“Change obj”将修改后的属性传递给children
预期行为:
我希望 BOTH children 不会被更新,因为它们都分离了变化检测器。
当前行为:
默认 child 未更新,但 OnPush child 已更新.. WHY?
它不应该因为它的 CD 是分离的...
场景2:
- 分离 OnPush 组件的 CD
- 编辑其内部值输入并单击更改内部:没有任何反应,因为 CD 已分离,因此未检测到更改。 . 好
- 单击 detectChanges():检测到更改并更新视图。到目前为止一切顺利。
- 再次编辑内部值输入并点击更改内部:再次,没有任何反应,因为CD是分离的,所以未检测到变化.. OK
- 编辑 parent 属性名字和姓氏。
- 点击“Change obj”将修改后的属性传递给children
预期行为:
OnPush children 根本不应该更新,再一次因为它的 CD 是分离的......CD 根本不应该在这个组件上发生
当前行为:
值和内部值都已更新,像完整 CD 一样的接缝应用于此组件。
- 最后一次,编辑内部值 输入并单击更改内部:检测到更改,并更新内部值。 ..
预期行为:
不应更新内部值,因为 CD 仍处于分离状态
当前行为:
检测到内部值变化... 为什么?
结论:
根据这些测试,我得出以下结论,这对我来说很奇怪:
- 具有 OnPush 策略的组件在其输入更改时得到 'changed detected',即使 他们的更改检测器已分离。
- 具有 OnPush 策略的组件会在每次输入更改时重新附加其更改检测器...
您如何看待这些结论?
你能用更好的方式解释这种行为吗?
这是错误还是预期的行为?
更新
Component with OnPush strategy get 'changed detected' when their input
changes, EVEN IF their change detector is detached.
因为 Angular 4.1.1 (2017-05-04) OnPush
应该尊重 detach()
https://github.com/angular/angular/commit/acf83b9
旧版本
有很多关于变化检测如何工作的未记录的东西。
我们应该注意三个主要的 changeDetection 状态 (cdMode
):
1) CheckOnce - 0
CheckedOnce
means that after calling detectChanges the mode of the
change detector will become Checked
.
AppViewclass
detectChanges(throwOnChange: boolean): void {
...
this.detectChangesInternal(throwOnChange);
if (this.cdMode === ChangeDetectorStatus.CheckOnce) {
this.cdMode = ChangeDetectorStatus.Checked; // <== this line
}
...
}
2) 已检查 - 1
Checked
means that the change detector should be skipped until its mode changes to CheckOnce
.
3) 分离 - 3
Detached
means that the change detector sub tree is not a part of
the main tree and should be skipped.
这里是使用Detached
的地方
AppViewclass
跳过内容检查
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
means that the change detector's mode will be set to CheckOnce
during hydration.
compiler/src/view_compiler/property_binder.ts
const directiveDetectChangesStmt = isOnPushComp ?
new o.IfStmt(directiveDetectChangesExpr, [compileElement.appElement.prop('componentView')
.callMethod('markAsCheckOnce', [])
.toStmt()]) : directiveDetectChangesExpr.toStmt();
让我们看看它在您的示例中的样子:
父工厂 (AppComponent)
然后再次回到 AppView class:
markAsCheckOnce(): void { this.cdMode = ChangeDetectorStatus.CheckOnce; }
场景一
1) Detach Change detector of OnPush Children and Default Children (click "detach()" on both components)
OnPush.cdMode - Detached
3) Click "Change obj" to pass the modified attribute to the children
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
正在开火。
结论如下:
Component with OnPush
strategy get 'changed detected' when their
input changes, EVEN IF their change detector is detached. Moreover
It changes view's status to CheckOnce
.
场景2
1) Detach CD for the OnPush component
OnPush.cdMode - Detached
6) Click "Change obj" to pass the modified attribute to the
children
See 3) from scenario 1 => OnPush.cdMode - Checked
7) For the last time, edit the internal value input and click change
internal: Change is detected, and internal value is updated ...
正如我上面提到的,所有事件处理程序都包括 markPathToRootAsCheckOnce
。所以:
markPathToRootAsCheckOnce
||
\/
OnPush.cdMode - CheckOnce
||
\/
OnPush.detectChanges
||
\/
OnPush.cdMode - Checked
如您所见,OnPush 策略和 ChangeDetector 管理一个 属性 - cdMode
Component with OnPush strategy get their change detector re attached
each time their input changed ...
最后我想说你是对的。
我正在尝试完全理解 Angular2 final 的变化检测。
这包括:
- 处理变化检测策略
- 在组件上附加和分离变化检测器。
我认为我已经对这些概念有了一个非常清晰的概述,但为了确保我的假设正确,我写了一个小插件来测试它们。
我对全局的一般理解是正确的,但在某些情况下,我有点迷茫。
这里是 plunker: Angular2 Change detection playground
plunker的快速解释:
很简单:
- 一个 parent 组件,您可以在其中编辑一个属性,该属性将传递给两个 children 组件:
- 开启 child,更改检测策略设置为 OnPush
- 开启 child,更改检测策略设置为默认值
parent 属性可以通过以下任一方式传递给 children 组件:
- 更改整个属性 object,并创建一个新属性(“Change obj”按钮)(在 OnPush child 上触发更改检测)
- 更改属性 object 中的成员("Change content" 按钮)(不会触发 OnPush child 上的更改检测)
对于每个 child 组件,可以附加或分离 ChangeDetector。 ("detach()" 和 "reattach()" 按钮)
OnPush child 有一个额外的内部 属性 可以编辑, 并且可以显式应用更改检测("detectChanges()" 按钮)
以下是我无法解释的行为的场景:
场景 1:
- 分离 OnPush Children 和默认 Children 的更改检测器(在两个组件上单击“detach()”)
- 编辑 parent 属性名字和姓氏
- 点击“Change obj”将修改后的属性传递给children
预期行为: 我希望 BOTH children 不会被更新,因为它们都分离了变化检测器。
当前行为: 默认 child 未更新,但 OnPush child 已更新.. WHY? 它不应该因为它的 CD 是分离的...
场景2:
- 分离 OnPush 组件的 CD
- 编辑其内部值输入并单击更改内部:没有任何反应,因为 CD 已分离,因此未检测到更改。 . 好
- 单击 detectChanges():检测到更改并更新视图。到目前为止一切顺利。
- 再次编辑内部值输入并点击更改内部:再次,没有任何反应,因为CD是分离的,所以未检测到变化.. OK
- 编辑 parent 属性名字和姓氏。
- 点击“Change obj”将修改后的属性传递给children
预期行为: OnPush children 根本不应该更新,再一次因为它的 CD 是分离的......CD 根本不应该在这个组件上发生
当前行为: 值和内部值都已更新,像完整 CD 一样的接缝应用于此组件。
- 最后一次,编辑内部值 输入并单击更改内部:检测到更改,并更新内部值。 ..
预期行为: 不应更新内部值,因为 CD 仍处于分离状态
当前行为: 检测到内部值变化... 为什么?
结论:
根据这些测试,我得出以下结论,这对我来说很奇怪:
- 具有 OnPush 策略的组件在其输入更改时得到 'changed detected',即使 他们的更改检测器已分离。
- 具有 OnPush 策略的组件会在每次输入更改时重新附加其更改检测器...
您如何看待这些结论?
你能用更好的方式解释这种行为吗?
这是错误还是预期的行为?
更新
Component with OnPush strategy get 'changed detected' when their input changes, EVEN IF their change detector is detached.
因为 Angular 4.1.1 (2017-05-04) OnPush
应该尊重 detach()
https://github.com/angular/angular/commit/acf83b9
旧版本
有很多关于变化检测如何工作的未记录的东西。
我们应该注意三个主要的 changeDetection 状态 (cdMode
):
1) CheckOnce - 0
CheckedOnce
means that after calling detectChanges the mode of the change detector will becomeChecked
.
AppViewclass
detectChanges(throwOnChange: boolean): void {
...
this.detectChangesInternal(throwOnChange);
if (this.cdMode === ChangeDetectorStatus.CheckOnce) {
this.cdMode = ChangeDetectorStatus.Checked; // <== this line
}
...
}
2) 已检查 - 1
Checked
means that the change detector should be skipped until its mode changes toCheckOnce
.
3) 分离 - 3
Detached
means that the change detector sub tree is not a part of the main tree and should be skipped.
这里是使用Detached
的地方
AppViewclass
跳过内容检查
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
means that the change detector's mode will be set toCheckOnce
during hydration.
compiler/src/view_compiler/property_binder.ts
const directiveDetectChangesStmt = isOnPushComp ?
new o.IfStmt(directiveDetectChangesExpr, [compileElement.appElement.prop('componentView')
.callMethod('markAsCheckOnce', [])
.toStmt()]) : directiveDetectChangesExpr.toStmt();
让我们看看它在您的示例中的样子:
父工厂 (AppComponent)
然后再次回到 AppView class:
markAsCheckOnce(): void { this.cdMode = ChangeDetectorStatus.CheckOnce; }
场景一
1) Detach Change detector of OnPush Children and Default Children (click "detach()" on both components)
OnPush.cdMode - Detached
3) Click "Change obj" to pass the modified attribute to the children
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
正在开火。
结论如下:
Component with
OnPush
strategy get 'changed detected' when their input changes, EVEN IF their change detector is detached. Moreover It changes view's status toCheckOnce
.
场景2
1) Detach CD for the OnPush component
OnPush.cdMode - Detached
6) Click "Change obj" to pass the modified attribute to the children
See 3) from scenario 1 => OnPush.cdMode - Checked
7) For the last time, edit the internal value input and click change internal: Change is detected, and internal value is updated ...
正如我上面提到的,所有事件处理程序都包括 markPathToRootAsCheckOnce
。所以:
markPathToRootAsCheckOnce
||
\/
OnPush.cdMode - CheckOnce
||
\/
OnPush.detectChanges
||
\/
OnPush.cdMode - Checked
如您所见,OnPush 策略和 ChangeDetector 管理一个 属性 - cdMode
Component with OnPush strategy get their change detector re attached each time their input changed ...
最后我想说你是对的。