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祖先标记为要检查。
当你的组件的ChangeDetectionStrategy是OnPush时,这是最主要的 。
OnPush本身意味着,只有在发生以下任何一种情况时才运行变更检测:
1-组件的@input之一已被完全replace为新值,或者简单地说,如果@Input属性的引用完全改变了。
所以如果你的组件的ChangeDetectionStrategy是OnPush ,那么你有:
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中更改检测的更多信息。