markForCheck()和detectChanges()

时间:2016-12-28 14:49:31

标签: angular angular2-changedetection

ChangeDetectorRef.markForCheck()ChangeDetectorRef.detectChanges()之间的区别是什么?

关于NgZone.run()之间的差异我只found information on SO,而不是这两个函数之间的区别。

对于仅提及该文档的答案,请说明一些实际场景,以选择其中一个。

4 个答案:

答案 0 :(得分:167)

来自docs:

  

detectChanges():void

     

Checks the change detector and its children.

这意味着,如果模型(您的类)中的任何内容发生了更改但未反映视图,则可能需要通知Angular检测这些更改(检测本地更改)并更新图。

可能的情况可能是:

1-更改检测器与视图分离(请参阅detach

2-更新已经发生,但它没有进入Angular Zone,因此,Angular不知道它。

类似于第三方功能更新了您的模型,并且您希望在此之后更新视图。

 someFunctionThatIsRunByAThirdPartyCode(){
     yourModel.text = "new text";
 }

因为此代码在Angular的区域之外(可能),您很可能需要确保检测更改并更新视图,因此:

 myFunction(){
   someFunctionThatIsRunByAThirdPartyCode();

   // Let's detect the changes that above function made to the model which Angular is not aware of.
    this.cd.detectChanges();
 }

注意

还有其他方法可以完成上述工作,换句话说,还有其他方法可以将这种变化带入Angular变革周期。

**你可以在zone.run中包装第三方函数:

 myFunction(){
   this.zone.run(this.someFunctionThatIsRunByAThirdPartyCode);
 }

**你可以将函数包装在setTimeout中:

myFunction(){
   setTimeout(this.someFunctionThatIsRunByAThirdPartyCode,0);
 }

3-在change detection cycle完成后,有些情况下你会更新模型,在这种情况下你会遇到这个可怕的错误:

“检查后表情已发生变化”;

这通常意味着(来自Angular2语言):

我看到您的模型中的一个更改是由我接受的方式之一(事件,XHR请求,setTimeout和......)然后我运行了更改检测以更新您的视图并完成了它,但随后在你的代码中有另一个函数再次更新模型,我不想再次运行我的更改检测,因为不再像AngularJS那样进行脏检查:D我们应该使用单向数据流!

你肯定会遇到这个错误:P。

解决问题的方法有很多:

1- 正确方法:确保更新在更改检测周期内(Angular2更新是一次发生一次的流程,不要在此之后更新模型并将代码移到更好的位置地点/时间)。

2- 懒惰方式:在更新之后运行detectChanges()以使angular2高兴,这绝对不是最好的方法,但是当你问到可能的场景是什么时,这就是其中之一

这样你就说:我真诚地知道你运行了变更检测,但我希望你再次这样做,因为我必须在完成检查后立即更新内容。

3-将代码放在setTimeout内,因为setTimeout已按区域修补,并在完成后运行detectChanges

来自文档

markForCheck() : void
     

Marks all ChangeDetectionStrategy ancestors as to be checked.

当您的组件的 ChangeDetectionStrategy OnPush 时,通常需要这样做。

OnPush本身意味着,只有在发生任何变化检测时才运行变更检测:

1-如果@Input属性的引用完全改变,则组件的一个@inputs已完全替换为新值,或者简单地放置。

因此,如果您的组件的 ChangeDetectionStrategy OnPush ,那么您有:

   var obj = {
     name:'Milad'
   };

然后你更新/改变它:

  obj.name = "a new name";

这不会更新 obj 引用,因此更改检测不会运行,因此视图不反映更新/突变。

在这种情况下,您必须手动告诉Angular检查并更新视图(markForCheck);

所以如果你这样做了:

  obj.name = "a new name";

你需要这样做:

  this.cd.markForCheck();

相反,bellow会导致更改检测运行:

    obj = {
      name:"a new name"
    };

使用新的{};

完全替换了之前的obj

2-事件已经触发,就像点击或类似事件或任何子组件发出事件一样。

事件如:

  • 点击
  • KEYUP
  • 订阅活动

简而言之:

  • 在角度运行变更检测后更新模型时,或者如果更新未处于角度世界中,请使用detectChanges()

  • 如果您正在使用OnPush,并且通过改变某些数据绕过markForCheck(),或者您已在 setTimeout 内更新模型,请使用ChangeDetectionStrategy ;

答案 1 :(得分:69)

两者之间的最大区别是detectChanges()实际上触发了变化检测,而markForCheck()不会触发变更检测。

detectChanges

这个用于运行组件树的更改检测,从您触发detectChanges()的组件开始。因此,更改检测将针对当前组件及其所有子组件运行。 Angular保存对ApplicationRef中根组件树的引用,当任何异步操作发生时,它通过包装器方法tick()触发对此根组件的更改检测:

@Injectable()
export class ApplicationRef_ extends ApplicationRef {
  ...
  tick(): void {
    if (this._runningTick) {
      throw new Error('ApplicationRef.tick is called recursively');
    }

    const scope = ApplicationRef_._tickScope();
    try {
      this._runningTick = true;
      this._views.forEach((view) => view.detectChanges()); <------------------

view这是根组件视图。我可以在What are the implications of bootstrapping multiple components中描述许多根组件。

@milad描述了您可能需要手动触发变更检测的原因。

markForCheck

正如我所说,这个人根本不会触发变化检测。它只是从当前组件向上移动到根组件,并将其视图状态更新为ChecksEnabled。这是源代码:

export function markParentViewsForCheck(view: ViewData) {
  let currView: ViewData|null = view;
  while (currView) {
    if (currView.def.flags & ViewFlags.OnPush) {
      currView.state |= ViewState.ChecksEnabled;  <-----------------
    }
    currView = currView.viewContainerParent || currView.parent;
  }
}

组件的实际更改检测未安排,但将来会发生(作为当前或下一个CD周期的一部分),即使它们具有分离的更改检测器,也将检查父组件视图。可以使用cd.detach()或指定OnPush更改检测策略来分离更改检测器。所有本机事件处理程序都标记所有父组件视图以供检查。

此方法通常用于ngDoCheck生命周期钩子。您可以在If you think ngDoCheck means your component is being checked — read this article中阅读更多内容。

有关详细信息,另请参阅Everything you need to know about change detection in Angular

答案 2 :(得分:12)

我制作了一个 4 分钟的截屏视频来解释 ma​​rkForCheck()detectChanges() 之间的区别 - https://www.youtube.com/watch?v=OcphK_aEd7I

enter image description here

答案 3 :(得分:6)

cd.detectChanges()将立即从当前组件一直到其后代运行更改检测。

cd.markForCheck()将不会运行更改检测,但会将其祖先标记为需要运行更改检测。下次更改检测可在任何地方运行,它也将对已标记的那些组件运行。

  • 如果要减少更改检测的次数,请使用cd.markForCheck()。通常,更改会影响多个组件,并且会在某处调用更改检测。您实际上是在说:让我们确保在发生这种情况时也对该组件进行了更新。 (该视图会在我编写的每个项目中立即更新,但不会在每个单元测试中更新)。
  • 如果不确定cd.detectChanges()当前不是 运行更改检测,请使用cd.markForCheck()。在这种情况下,detectChanges()将出错。这可能意味着您试图编辑祖先组件的状态,这违背了围绕Angular进行变更检测的假设。
  • 如果在执行其他操作之前同步更新视图至关重要,请使用detectChanges()markForCheck()可能实际上并未及时更新您的视图。单元测试会影响您的视图,例如,在应用程序本身不需要时,可能需要您手动调用fixture.detectChanges()
  • 如果您要更改组件中祖先的数量超过后代的状态,则可以使用detectChanges()来提高性能,因为您不必在组件的祖先上运行更改检测。