简体   繁体   English

Angular2变化检测误解 - 用plunker

[英]Angular2 change detection misunderstanding - With plunker

I'm trying to fully understand change detection with Angular2 final. 我正在尝试用Angular2 final完全理解变化检测。

This include: 这包括:

  • Dealing with change detection strategies 处理变更检测策略
  • Attaching and detaching change detector from a component. 从组件上安装和拆卸更换检测器。

I thought I already got a pretty clear overview of those concepts, but to make sure my assumptions where right, I wrote a small plunkr to test them. 我以为我已经对这些概念进行了非常明确的概述,但为了确保我的假设正确,我写了一个小傻瓜来测试它们。

My general understanding about that where globally right, but in some situations, I get a little bit lost. 我对全球正确的一般理解,但在某些情况下,我有点迷失。


Here is the plunker: Angular2 Change detection playground 这是plunker: Angular2变化检测操场

Quick explanation of the plunker: 对plunker的快速解释:

Pretty simple: 很简单:

  • One parent component where you can edit one attribute that will get passed down to two children components: 一个父组件,您可以在其中编辑一个将传递给两个子组件的属性:
  • On child with change detection strategy set to OnPush 将具有变化检测策略的孩子设置为OnPush
  • On child with change detection strategy set to Default 将具有更改检测策略的子项设置为“默认”

The parent attribute can be passed to children components by either: parent属性可以通过以下任一方式传递给子组件:

  • Changing the whole attribute object, and creating a new one (" Change obj " button) (which trigger change detection on the OnPush child) 更改整个属性对象,并创建一个新对象(“ 更改obj ”按钮)(触发OnPush子项上的更改检测)
  • Changing the members inside the attribute object ( "Change content" button) (which do not trigger change detection on the OnPush child) 更改属性对象内的成员( “更改内容”按钮)(不会触发OnPush子项上的更改检测)

For each child component, ChangeDetector can be attached or detached. 对于每个子组件,可以附加或分离ChangeDetector。 ( "detach()" and "reattach()" buttons) “detach()”“reattach()”按钮)

OnPush child have an additional internal property that can be edited, and change detection can be explicitly applied ( "detectChanges()" button) OnPush子项具有可以编辑的附加内部属性,并且可以显式应用更改检测( “detectChanges()”按钮)


Here are the scenarios where I get behaviours that I cannot explain: 以下是我得到无法解释的行为的场景:

Scenario1: Scenario1:

  1. Detach Change detector of OnPush Children and Default Children (click " detach() " on both components) 分离OnPush Children和Default Children的更改检测器(单击两个组件上的“ detach() ”)
  2. Edit the parent attribute firstname and lastname 编辑父属性firstname和lastname
  3. Click " Change obj " to pass the modified attribute to the children 单击“ 更改obj ”将修改后的属性传递给子项

Expected behavior: I expect BOTH children NOT to be updated, because they both have their change detector detached. 预期的行为:我希望两个孩子都不会被更新,因为他们都有自己的变化探测器。

Current behavior: Default child is not updated, but OnPush child is updated .. WHY? 当前行为:默认子项未更新,但OnPush子项已更新.. 为什么? It shouldn't because its CD is detached ... 它不应该因为它的CD分离...

Scenario2: Scenario2:

  1. Detach CD for the OnPush component 为OnPush组件分离CD
  2. Edit its internal value input and click change internal : Nothing happen, because CD is detached, so change is not detected... OK 编辑其内部值输入并单击更改内部 :没有任何反应,因为CD已分离,因此未检测到更改...确定
  3. Click detectChanges() : changes are detected and the view is updated. 单击detectChanges() :检测到更改并更新视图。 So far so good. 到现在为止还挺好。
  4. Once again, edit the internal value input and click change internal : Once again, nothing happen, because CD is detached, so change is not detected.. OK 再一次,编辑内部值输入并单击更改内部 :再次,没有任何事情发生,因为CD已分离,因此未检测到更改..确定
  5. Edit the parent attribute firstname and lastname. 编辑父属性firstname和lastname。
  6. Click " Change obj " to pass the modified attribute to the children 单击“ 更改obj ”将修改后的属性传递给子项

Expected behavior: OnPush children should NOT be updated at ALL, once again because its CD is detached...CD should not happen at all on this component 预期的行为: OnPush的孩子不应该全部更新,再次因为它的CD被分离...... CD不应该在这个组件上发生

Current behavior: Both the value and the internal values are updated, seams like a full CD is applied to this component. 当前行为:更新值和内部值,将完整CD等接缝应用于此组件。

  1. For the last time, edit the internal value input and click change internal : Change is detected, and internal value is updated... 最后一次,编辑内部值输入并单击更改内部 :检测到更改,并更新内部值...

Expected behavior: Internal value should NOT be updated because CD is still detached 预期行为:不应更新内部值,因为CD仍然已分离

Current behavior: Internal value change is detected... WHY? 当前行为:检测到内部值变化... 为什么?


Conclusions: 结论:

According to those tests I conclude the following, which seams strange to me: 根据那些测试,我得出以下结论,这对我来说很奇怪:

  • Component with OnPush strategy get 'changed detected' when their input changes, EVEN IF their change detector is detached. 具有OnPush策略的组件在其输入发生变化时会“检测到已更改”, 即使它们的变化检测器已分离。
  • Component with OnPush strategy get their change detector re attached each time their input changed... 具有OnPush策略的组件每次输入更改时都会重新连接其变化检测器...

What do you think about those conclusions? 您如何看待这些结论?

Can you explain this behavior in a better way? 你能以更好的方式解释这种行为吗?

Is this a bug or the desired behavior? 这是一个错误还是想要的行为?

Update 更新

Component with OnPush strategy get 'changed detected' when their input changes, EVEN IF their change detector is detached. 具有OnPush策略的组件在其输入发生变化时会“检测到已更改”,即使它们的变化检测器已分离。

Since Angular 4.1.1 (2017-05-04) OnPush should respect detach() Angular 4.1.1 (2017-05-04) OnPush应尊重detach()

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

Old version 旧版

There are a lot of undocumented stuff about how change detection works. 关于变更检测如何工作,有很多未记载的内容。

We should be aware about three main changeDetection statuses ( cdMode ): 我们应该知道三个主要的changeDetection状态cdMode ):

1) CheckOnce - 0 1) CheckOnce - 0

CheckedOnce means that after calling detectChanges the mode of the change detector will become Checked . CheckedOnce表示在调用detectChanges后,更改检测器的模式将变为Checked

AppView class AppView类

detectChanges(throwOnChange: boolean): void {
  ...
  this.detectChangesInternal(throwOnChange);
  if (this.cdMode === ChangeDetectorStatus.CheckOnce) {
    this.cdMode = ChangeDetectorStatus.Checked; // <== this line
  }
  ...
}

2) Checked - 1 2) 检查 - 1

Checked means that the change detector should be skipped until its mode changes to CheckOnce . Checked表示应跳过更改检测器,直到其模式更改为CheckOnce

3) Detached - 3 3) 独立 - 3

Detached means that the change detector sub tree is not a part of the main tree and should be skipped. Detached意味着变化检测器子树不是主树的一部分,应该被跳过。

Here are places where Detached is used 以下是使用Detached地方

AppView class AppView类

Skip content checking 跳过内容检查

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);
  }
}

Skip view checking 跳过视图检查

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);
  }
}

Skip changing cdMode to CheckOnce 跳过将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;
  }
}

Note: markPathToRootAsCheckOnce is running in all event handlers of your view: 注意: markPathToRootAsCheckOnce在视图的所有事件处理程序中运行:

在此输入图像描述

So if set status to Detached then your view won't be changed. 因此,如果将状态设置为Detached则不会更改您的视图。

Then how works OnPush strategy 然后如何运作OnPush策略

OnPush means that the change detector's mode will be set to CheckOnce during hydration. OnPush意味着更换检测器的模式将在水合过程中设置为CheckOnce

compiler/src/view_compiler/property_binder.ts 编译器/ 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 https://github.com/angular/angular/blob/2.1.2/modules/%40angular/compiler/src/view_compiler/property_binder.ts#L193-L197

Let's see how it looks in your example: 让我们看看它在你的例子中的样子:

Parent factory (AppComponent) 父工厂(AppComponent)

在此输入图像描述

And again back to the AppView class : 再次回到AppView类

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

Scenario 1 场景1

1) Detach Change detector of OnPush Children and Default Children (click "detach()" on both components) 1)分离OnPush Children和Default Children的Change变换器(点击两个组件上的“detach()”)

OnPush.cdMode - Detached

3) Click "Change obj" to pass the modified attribute to the children 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

Therefore OnPush.dectectChanges is firing. 因此OnPush.dectectChanges正在解雇。

Here is conclusion: 这是结论:

Component with OnPush strategy get 'changed detected' when their input changes, EVEN IF their change detector is detached. 具有OnPush策略的组件在其输入发生变化时会“检测到已更改”,即使它们的变化检测器已分离。 Moreover It changes view's status to CheckOnce . 此外,它将视图的状态更改为CheckOnce

Scenario2 Scenario2

1) Detach CD for the OnPush component 1)为OnPush组件分离CD

OnPush.cdMode - Detached

6) Click "Change obj" to pass the modified attribute to the children 6)单击“更改obj”将修改后的属性传递给子项

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 ... 7)最后一次,编辑内部值输入并单击更改内部:检测到更改,并更新内部值...

As I mentioned above, all event handlers includes markPathToRootAsCheckOnce . 如上所述,所有事件处理程序都包含markPathToRootAsCheckOnce So: 所以:

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

As you can see OnPush strategy and ChangeDetector manage one property - cdMode 正如您所见,OnPush策略和ChangeDetector管理一个属性 - cdMode

Component with OnPush strategy get their change detector re attached each time their input changed ... 具有OnPush策略的组件每次输入更改时都会重新连接其变化检测器...

In conclusion I want to say that seems you're right. 总之,我想说你似乎是对的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM