我试图用Angular2 final完全理解变化检测。
这包括:
我认为我已经清楚地了解了这些概念,但为了确保我的假设正确,我写了一个小傻瓜来测试它们。
我对全球正确的一般理解,但在某些情况下,我有点迷失。
以下是plunker:Angular2 Change detection playground
对弹药的快速解释:
非常简单:
父属性可以通过以下任一方式传递给子组件:
对于每个子组件,可以附加或分离ChangeDetector。 (" detach()" 和" reattach()" 按钮)
OnPush child有一个可以编辑的附加内部属性, 并且可以明确应用更改检测(" detectChanges()" 按钮)
以下是我得到无法解释的行为的情景:
Scenario1:
预期行为 我希望 BOTH 的孩子不会被更新,因为他们都更换了变量探测器。
当前行为: 默认子项未更新,但OnPush子项已更新.. 为什么? 它不应该因为它的CD分离......
Scenario2:
预期行为 OnPush的孩子不应该全部更新,因为它的CD是分离的...... CD不应该在这个组件上发生
当前行为: 值和内部值都会更新,像完整CD这样的接缝会应用于此组件。
预期行为 不应更新内部值,因为CD仍然已分离
当前行为: 检测到内部价值变化...... 为什么?
结论:
根据这些测试,我得出以下结论,这对我来说很奇怪:
您如何看待这些结论?
你能以更好的方式解释这种行为吗?
这是一个错误还是想要的行为?
答案 0 :(得分: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
}
...
}
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();
让我们看看它在你的例子中的样子:
父工厂(AppComponent)
再次回到 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策略的组件重新连接其变化检测器 每次他们的输入都改变了......
总之,我想说,你似乎是对的。