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

在Angular中, ChangeDetectorRef.markForCheck()ChangeDetectorRef.detectChanges()什么区别?

我只发现了关于 NgZone.run()之间的区别的信息 ,但是在这两个函数之间却没有。

只有参考文档的答案,请说明一些实际的情况下select一个在另一个? 这将有助于在我脑海中澄清。

从文档:

 detectChanges() : void 

检查更换探测器及其子。

这意味着如果您的模型(您的类)中的任何事物发生了变化,但并未反映该视图,则可能需要通知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中包装第三方function:

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

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

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

3-在更改检测周期完成之后,还有一些情况会更新模型,在这些情况下,您会遇到这个可怕的错误:

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

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

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

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

几个方法来解决它:

1- 正确的方法 :确保更新是在变化检测(Angular2更新是单向stream发生一次,不要更新模型后,并将您的代码移动到一个更好的地方/时间)。

2- 懒惰的方式:更新后运行detectChanges()使angular2开心,这绝对不是最好的方法,但当你问什么是可能的情况下,这是其中之一。

这样你就会说:我真诚地知道你运行了变化检测,但我不想再做这个,因为在完成检查之后,我不得不更新一些东西。

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


从文档

  markForCheck() : void 

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

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

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

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

所以如果你的组件的ChangeDetectionStrategyOnPush ,那么你有:

  var obj = { name:'Milad' }; 

然后你更新/改变它:

  obj.name = "a new name"; 

这将不会更新obj参考,因此变化检测不会运行,因此视图不反映更新/突变。

相反,波纹pipe会导致变化检测运行:

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

用新的{}完全替代之前的obj;

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

所以如果你这样做了:

  obj.name = "a new name"; 

你需要这样做:

  this.cd.markForCheck(); 

2-事件已经发生,像点击或类似的事情,或者任何一个子组件已经发射了一个事件。

事件如:

  • 点击
  • KEYUP
  • 订阅事件
  • 等等。

所以简而言之:

  • 在angular更新模型之后使用detectChanges() ,它已经运行了更改检测,或者更新还没有完全处于angular度世界。

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

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

detectChanges

这个是用来运行变化检测的组件树,从你触发detectChanges()的组件开始。 因此,变化检测将针对当前组件及其所有子组件运行。 Angular在ApplicationRef拥有对根组件树的引用,当发生任何asynchronous操作时,它会通过包装方法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中更改检测的更多信息。