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

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

对于NgZone.run()之间的区别,我只找到关于SO的信息,而不是这两个函数之间的区别。

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

128245 次浏览

detectChanges(): void

< a href = " https://angular.io/docs/js/latest/api/core/index/ChangeDetectorRef-class.html !#detectChanges-anchor" rel="noreferrer">检查更改检测器及其子探测器

这意味着,如果你的模型(你的类)中有任何东西发生了变化,但没有反映到视图中,你可能需要通知Angular来检测这些变化(检测局部变化)并更新视图。

可能的场景可能是:

1-更改检测器从视图中分离(参见分离)

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-也有在change detection cycle结束后更新模型的情况,在这些情况下,你会得到这个可怕的错误:

“经过检查,表达式发生了变化”;;

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

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

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

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

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

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

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

3-将代码放在setTimeout中,因为setTimeout是按区域打补丁的,在完成后将运行detectChanges


来自文档

markForCheck() : void

< a href = " https://angular.io/docs/js/latest/api/core/index/ChangeDetectorRef-class.html !#markForCheck-anchor" rel="noreferrer">标记所有ChangeDetectionStrategy祖先作为检查对象

当你的组件的ChangeDetectionStrategyOnPush时,这是最需要的。

OnPush本身意味着,如果发生了以下任何情况,只运行更改检测:

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

所以如果你的组件的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;

2-事件已经触发,比如点击或者类似的事情,或者任何子组件已经触发事件。

活动包括:

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

简而言之:

  • 当你在angular运行了它的更改检测之后更新了模型,或者如果这个更新根本就没有在angular中进行,就使用detectChanges()

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

两者之间最大的区别是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中的变更检测,你需要知道的一切

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

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

  • 如果你想减少更改检测被称为cd.markForCheck()的次数。更改通常会影响多个组件,在某些地方会调用更改检测。你实际上是在说:让我们确保这个组件在发生这种情况时被更新。(在我编写的每个项目中,视图都会立即更新,但不是在每个单元测试中)。
  • 如果你不能确定cd.detectChanges()不是目前 运行变更检测,使用cd.markForCheck()。在这种情况下,detectChanges()将出错。这可能意味着你试图编辑一个祖先组件的状态,这与Angular的变更检测设计的假设相悖。
  • 如果在某些其他操作之前同步更新视图非常重要,则使用detectChanges()markForCheck()可能不会及时更新你的视图。例如,单元测试会影响你的视图,可能需要你手动调用fixture.detectChanges(),而在应用程序本身中并不需要这样做。
  • 如果你正在改变一个组件的状态,它的祖先元素比后代元素多,你可以通过使用detectChanges()来提高性能,因为你没有必要在组件的祖先元素上运行变更检测。

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

enter image description here