238

ChangeDetectorRef.markForCheck()和 和有什么不一样ChangeDetectorRef.detectChanges()

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

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

4

4 回答 4

310

检测变化():无效

检查变化检测器及其子代。

这意味着,如果您的模型(您的类)中的任何东西发生了变化,但它没有反映视图,您可能需要通知 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- 将代码放在 asetTimeout中,因为setTimeout由区域修补,detectChanges完成后将运行。


从文档

markForCheck() : void

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

当您的组件的ChangeDetectionStrategyOnPush时,这主要是需要的。

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

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

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

   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 替换了以前的 obj {}

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

像这样的事件:

  • 点击
  • 键位
  • 订阅事件
  • 等等

简而言之:

  • detectChanges()当您在 Angular 运行更改检测后更新模型时使用,或者如果更新根本不在 Angular 世界中。

  • 如果您正在使用markForCheck()OnPush 并且您ChangeDetectionStrategy通过改变一些数据来绕过,或者您已经在setTimeout中更新了模型,请使用此选项;

于 2016-12-28T14:54:50.977 回答
134

两者最大的区别在于detectChanges()实际触发变更检测,而markForCheck()不会触发变更检测。

检测变化

这个用于对从您触发的组件开始的组件树运行更改检测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 描述了您可能需要手动触发变更检测的原因。

标记检查

正如我所说,这家伙根本不会触发变更检测。它只是从当前组件向上移动到根组件并将其视图状态更新为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中更改检测的所有信息。

于 2017-07-30T05:40:51.767 回答
24

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

在此处输入图像描述

于 2020-12-19T17:29:51.183 回答
21

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

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

  • 如果要减少更改检测的次数,则称为使用cd.markForCheck()。通常,更改会影响多个组件,并且会在某个地方调用更改检测。您本质上是在说:让我们确保在发生这种情况时更新此组件。(视图会在我编写的每个项目中立即更新,但不是在每个单元测试中)。
  • 如果您不能确定当前cd.detectChanges()没有运行更改检测,请使用. 在这种情况下会出错。这可能意味着您试图编辑祖先组件的状态,这与 Angular 的变更检测设计的假设背道而驰。 cd.markForCheck()detectChanges()
  • 如果视图在其他操作之前同步更新很重要,请使用detectChanges(). markForCheck()实际上可能不会及时更新您的视图。例如,单元测试会影响您的视图,可能需要您fixture.detectChanges()在应用程序本身不需要时手动调用。
  • 如果您要更改祖先多于后代的组件中的状态,则可以通过使用来提高性能,detectChanges()因为您不必在组件的祖先上运行更改检测。
于 2020-06-17T21:35:39.523 回答