markForCheck()和detectChanges()有什么区别


174

ChangeDetectorRef.markForCheck()和之间有什么区别ChangeDetectorRef.detectChanges()

我只找到了关于SO的信息NgZone.run(),但没有找到这两个功能之间的区别。

对于仅参考文档的答案,请举例说明一些实际情况,以供选择。



@Milad你怎么知道他反对?有很多人仔细阅读此站点。
再见StackExchange

2
@FrankerZ,因为我正在写书,并且看到了选票,然后又更新了第二个问题,说:“对于仅参考文档的答案,请举例说明一些实际情况,以便从中选择一个?这将有助于澄清它在我脑海里”。
米拉德(Milad)

3
不利的一面是激励您完成原始答案,该答案只是从我已经看到的文档中复制并粘贴的。而且有效!现在的答案已经非常清楚了,是可以接受的答案,谢谢:)
议会

3
@议会多么狡猾的计划!
HankCa's

Answers:


234

从文档:

detectChanges():无效

检查变更检测器及其子级。

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

可能的情况可能是:

1-变更检测器从视图分离(请参阅分离

2-更新已发生,但尚未在Angular区域内,因此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

将所有ChangeDetectionStrategy祖先标记为要检查。

这主要是在需要时ChangeDetectionStrategy你的组件是OnPush

OnPush本身意味着,仅在发生以下任何一种情况时才运行更改检测:

1-如果@Input属性的引用已完全更改,则组件的@input之一已完全替换为新值,或者简单地放置了该内容。

所以,如果ChangeDetectionStrategy您的组件是OnPush,然后你必须:

   var obj = {
     name:'Milad'
   };

然后您像这样更新/更改它:

  obj.name = "a new name";

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

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

因此,如果您这样做:

  obj.name = "a new name";

您需要这样做:

  this.cd.markForCheck();

相反,下面将导致运行更改检测:

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

用新的完全替换了以前的obj {};

2-事件已触发,例如单击或类似的事件,或者任何子组件已发出事件。

像这样的事件:

  • 请点击
  • 键控
  • 订阅事件
  • 等等

简而言之:

  • detectChanges()在angular运行之后更新模型时使用,它可以检测变化,或者根本不存在更新。

  • 使用markForCheck()如果您使用的OnPush和你绕过ChangeDetectionStrategy通过突变的一些数据,或者你已经更新了内部模型的setTimeout ;


6
因此,如果您对该obj进行了变异,则该视图将不会被更新,即使您运行detectChanges,该视图也不会起作用,因为没有任何更改 -这是不正确的。detectChanges更新视图。请参阅此深入说明
Max Koretskyi

关于结论中的markForCheck,它也不准确。这里的变形例这个问题,它不检测与OnPush和markForCheck对象的变化。但是如果没有OnPush策略,则相同的示例将起作用
Estus Flask

@Maximus,关于您的第一条评论,我阅读了您的帖子,非常感谢。但是在您的解释中,您要说的是如果该策略是OnPush,则意味着如果this.cdMode === ChangeDetectorStatus.Checked它不会更新视图,那么这就是为什么要使用markForCheck的原因。
Milad

关于您与pl车手的链接,两个示例对我来说都很好,我不知道您的意思
Milad

@Milad,这些评论来自@estus :)。我的大约detectChangescdModeAngular中没有4.x.x。我在我的文章中写到。很开心你喜欢。别忘了您可以在媒体上推荐它,也可以关注我:)
Max Koretskyi

99

两者之间最大的区别是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这是根组件视图。正如我在“ 引导多个组件的含义是什么”中所述,可能有许多根组件

@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生命周期挂钩中使用。如果您认为这ngDoCheck意味着正在检查您的组件,请阅读更多内容阅读本文

另请参阅有关Angular中的更改检测所需了解的所有内容。


1
为什么在组件及其祖先上,detectChanges在组件及其子级上起作用而在markForCheck上起作用?
pablo

@pablo,这是设计使然。我对基本原理不是很熟悉
Max Koretskyi

@ AngularInDepth.com如果处理非常密集,changedetection是否会阻止UI?
alt255

1
@jerry,推荐的方法是使用异步管道,该管道在内部跟踪预订,并在每个新值触发器上进行跟踪markForCheck。因此,如果您不使用异步管道,那可能就是您应该使用的管道。但是,请记住,商店更新应该是由于某些异步事件而发生的,以便启动更改检测。大多数情况下都是这样。但也有例外blog.angularindepth.com/...
马克斯Koretskyi

1
@MaxKoretskyiakaWizard感谢您的回复。是的,商店更新主要是由于获取或设置isFetching before导致的。并在获取之后..但是我们不能总是使用,async pipe因为在订阅内部,我们通常有一些事情要做call setFromValues do some comparison..如果async本身调用markForCheck它,那么如果我们自己调用它会带来什么问题呢?但是同样,在ngOnInit获取不同的数据时,我们通常会有2-3个或更多的选择器...而我们都调用markForCheck了所有选择器。
jerry

0

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

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

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