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

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

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


当前回答

最大的区别是markForCheck会检查当前组件的绑定,而不是调用子视图中的DoCheck()之类的生命周期钩子,但detectChange()会!

其他回答

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

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

If you want to reduce the number of times change detection is called use cd.markForCheck(). Often, changes affect multiple components and somewhere change detection will be called. You're essentially saying: let's just make sure this component is also updated when that happens. (The view is immediately updated in every project I've written, but not in every unit test). If you can't be sure that cd.detectChanges() isn't currently running change detection, use cd.markForCheck(). detectChanges() will error in that case. This probably means you trying to edit the state of an ancestor component, which is working against the assumptions that Angular's change detection are designed around. If it's critical that the view update synchronously before some other action, use detectChanges(). markForCheck() may not actually update your view in time. Unit testing something affects your view, for example, may require you manually call fixture.detectChanges() when that wasn't necessary in the app itself. If you are changing the state in a component with more ancestors than descendants you may get a performance increase by using detectChanges() since you aren't unnecessarily running change detection on the component's ancestors.

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

两者之间最大的区别是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()); <------------------

这里的视图是根组件视图。可以有许多根组件,正如我在引导多个组件的含义中所描述的那样。

@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中关于变更检测你需要知道的一切。

detectChanges(): void 检查更改检测器及其子程序。

这意味着,如果你的模型(你的类)中有任何东西发生了变化,但没有反映到视图中,你可能需要通知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中。

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

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

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

3-也有在变更检测周期结束后更新模型的情况,在这些情况下,你会得到这个可怕的错误:

“检查后表达式有变化”;

这通常意味着(在Angular2语言中):

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

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

有几种方法可以解决这个问题:

1-正确的方式:确保更新是在变更检测周期内(Angular2的更新是单向流,只发生一次,不要在那之后更新模型,把你的代码移到一个更好的地方/时间)。

2-惰性方法:在更新后运行detectChanges()来让angar2开心,这绝对不是最好的方法,但正如你问的可能的场景,这是其中之一。

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

3-把代码放在一个setTimeout中,因为setTimeout是按区域打补丁的,在它完成后会运行detectChanges。


来自文档

markForCheck():无效 将所有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-事件已经触发,比如点击或者类似的事情,或者任何子组件已经触发事件。

活动包括:

点击 按键弹起 订阅事件 等。

简而言之:

当你在angular运行了它的更改检测之后更新了模型,或者如果更新根本没有在angular中进行,就使用detectChanges()。 使用markForCheck()如果你正在使用OnPush,你正在绕过ChangeDetectionStrategy通过突变一些数据或你已经更新了setTimeout内的模型;

最大的区别是markForCheck会检查当前组件的绑定,而不是调用子视图中的DoCheck()之类的生命周期钩子,但detectChange()会!