markForCheck() 和 detectChanges() 有什么区别
What's the difference between markForCheck() and detectChanges()
ChangeDetectorRef.markForCheck()
和ChangeDetectorRef.detectChanges()
有什么区别?
我只关于NgZone.run()
之间的区别,而不是这两个功能之间的区别。
对于仅参考文档的答案,请说明一些实际场景以选择一个。
detectChanges() : void
这意味着,如果你的模型(你的class)内部的任何东西发生了变化但没有反映视图,你可能需要通知Angular来检测这些更改(检测本地更改)并更新视图。
可能的情况可能是:
1- 变化检测器与视图分离(参见 detach)
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 更新是一次发生的单向流程,不要更新之后建模并将您的代码移动到更好的 place/time ).
2- 惰性方式: 运行 detectChanges() 更新后让 angular2 开心,这绝对不是最好的方式,但是正如你问的可能的情况是什么,这是其中之一。
这样你是说:我真诚地了解你 运行 变化检测,但我希望你再做一次,因为我必须在你完成检查后即时更新一些东西。
3-把代码放在setTimeout
里面,因为setTimeout
是按zone打补丁的,打完后会运行detectChanges
来自文档
markForCheck() : void
Marks all ChangeDetectionStrategy ancestors as to be checked.
当您的组件的 ChangeDetectionStrategy 为 OnPush.
时,最需要这样做
OnPush 本身意味着,只有 运行 如果发生了任何这些变化检测:
1- 组件的 @inputs 之一已完全替换为新值,或者简单地说,如果 @Input 属性 的引用已完全更改。
所以如果你的组件的 ChangeDetectionStrategy 是 OnPush 那么你有:
var obj = {
name:'Milad'
};
然后你 update/mutate 它喜欢 :
obj.name = "a new name";
这不会更新 obj 引用,因此不会进行更改检测 运行,因此视图不会反映 update/mutation。
在这种情况下,您必须手动告诉 Angular 检查和更新视图 (markForCheck);
因此,如果您这样做了:
obj.name = "a new name";
您需要这样做:
this.cd.markForCheck();
相反,下面会导致对 运行 的更改检测:
obj = {
name:"a new name"
};
用新的 {}
;
完全替换了之前的 obj
2- 触发了一个事件,例如点击或类似的东西,或者任何子组件发出了一个事件。
类似事件:
- 点击
- 键盘输入
- 订阅事件
- 等等
简而言之:
当你在 angular 之后更新模型时使用 detectChanges()
运行 它是变化检测,或者如果更新还没有在 angular整个世界。
如果您正在使用 OnPush 并且您通过改变一些数据绕过 ChangeDetectionStrategy
或者您已经更新了 [=106= 中的模型,请使用 markForCheck()
]setTimeout;
两者最大的区别是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 描述了您可能需要手动触发更改检测的原因。
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
生命周期挂钩中。您可以在 If you think ngDoCheck
means your component is being checked — read this article.
中阅读更多内容
另请参阅 Everything you need to know about change detection in Angular 了解更多详细信息。
cd.detectChanges()
将 运行 从当前组件向下通过其后代立即更改检测。
cd.markForCheck()
不会 运行 更改检测,但会将其祖先标记为需要 运行 更改检测。下次在任何地方进行变更检测 运行 时,它也会 运行 那些被标记的组件。
- 如果要减少调用更改检测的次数,请使用
cd.markForCheck()
。通常,更改会影响多个组件,并且会在某处调用更改检测。您实际上是在说:让我们确保此组件在发生这种情况时 也 已更新。 (视图会在我编写的每个项目中立即更新,但不会在每个单元测试中更新)。
- 如果您不能确定
cd.detectChanges()
不是 当前 运行ning 变更检测, 使用 cd.markForCheck()
。 detectChanges()
在这种情况下会出错。这可能意味着您试图编辑祖先组件的状态,这与 Angular 的变更检测是围绕其设计的假设相悖的。
- 如果视图在某些其他操作之前同步更新很重要,请使用
detectChanges()
。 markForCheck()
可能不会及时更新您的观点。单元测试某些东西会影响您的视图,例如,可能需要您在应用程序本身不需要时手动调用 fixture.detectChanges()
。
- 如果您要更改祖先多于后代的组件中的状态,您可以通过使用
detectChanges()
来提高性能,因为您不必 运行 对组件的祖先进行更改检测.
我创建了一个 4 分钟的截屏视频来解释 markForCheck() 和 detectChanges() - https://www.youtube.com/watch?v=OcphK_aEd7I 之间的区别
ChangeDetectorRef.markForCheck()
和ChangeDetectorRef.detectChanges()
有什么区别?
我只NgZone.run()
之间的区别,而不是这两个功能之间的区别。
对于仅参考文档的答案,请说明一些实际场景以选择一个。
detectChanges() : void
这意味着,如果你的模型(你的class)内部的任何东西发生了变化但没有反映视图,你可能需要通知Angular来检测这些更改(检测本地更改)并更新视图。
可能的情况可能是:
1- 变化检测器与视图分离(参见 detach)
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 更新是一次发生的单向流程,不要更新之后建模并将您的代码移动到更好的 place/time ).
2- 惰性方式: 运行 detectChanges() 更新后让 angular2 开心,这绝对不是最好的方式,但是正如你问的可能的情况是什么,这是其中之一。
这样你是说:我真诚地了解你 运行 变化检测,但我希望你再做一次,因为我必须在你完成检查后即时更新一些东西。
3-把代码放在setTimeout
里面,因为setTimeout
是按zone打补丁的,打完后会运行detectChanges
来自文档
markForCheck() : void
Marks all ChangeDetectionStrategy ancestors as to be checked.
当您的组件的 ChangeDetectionStrategy 为 OnPush.
时,最需要这样做OnPush 本身意味着,只有 运行 如果发生了任何这些变化检测:
1- 组件的 @inputs 之一已完全替换为新值,或者简单地说,如果 @Input 属性 的引用已完全更改。
所以如果你的组件的 ChangeDetectionStrategy 是 OnPush 那么你有:
var obj = {
name:'Milad'
};
然后你 update/mutate 它喜欢 :
obj.name = "a new name";
这不会更新 obj 引用,因此不会进行更改检测 运行,因此视图不会反映 update/mutation。
在这种情况下,您必须手动告诉 Angular 检查和更新视图 (markForCheck);
因此,如果您这样做了:
obj.name = "a new name";
您需要这样做:
this.cd.markForCheck();
相反,下面会导致对 运行 的更改检测:
obj = {
name:"a new name"
};
用新的 {}
;
2- 触发了一个事件,例如点击或类似的东西,或者任何子组件发出了一个事件。
类似事件:
- 点击
- 键盘输入
- 订阅事件
- 等等
简而言之:
当你在 angular 之后更新模型时使用
detectChanges()
运行 它是变化检测,或者如果更新还没有在 angular整个世界。如果您正在使用 OnPush 并且您通过改变一些数据绕过
ChangeDetectionStrategy
或者您已经更新了 [=106= 中的模型,请使用markForCheck()
]setTimeout;
两者最大的区别是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 描述了您可能需要手动触发更改检测的原因。
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
生命周期挂钩中。您可以在 If you think ngDoCheck
means your component is being checked — read this article.
另请参阅 Everything you need to know about change detection in Angular 了解更多详细信息。
cd.detectChanges()
将 运行 从当前组件向下通过其后代立即更改检测。
cd.markForCheck()
不会 运行 更改检测,但会将其祖先标记为需要 运行 更改检测。下次在任何地方进行变更检测 运行 时,它也会 运行 那些被标记的组件。
- 如果要减少调用更改检测的次数,请使用
cd.markForCheck()
。通常,更改会影响多个组件,并且会在某处调用更改检测。您实际上是在说:让我们确保此组件在发生这种情况时 也 已更新。 (视图会在我编写的每个项目中立即更新,但不会在每个单元测试中更新)。 - 如果您不能确定
cd.detectChanges()
不是 当前 运行ning 变更检测, 使用cd.markForCheck()
。detectChanges()
在这种情况下会出错。这可能意味着您试图编辑祖先组件的状态,这与 Angular 的变更检测是围绕其设计的假设相悖的。 - 如果视图在某些其他操作之前同步更新很重要,请使用
detectChanges()
。markForCheck()
可能不会及时更新您的观点。单元测试某些东西会影响您的视图,例如,可能需要您在应用程序本身不需要时手动调用fixture.detectChanges()
。 - 如果您要更改祖先多于后代的组件中的状态,您可以通过使用
detectChanges()
来提高性能,因为您不必 运行 对组件的祖先进行更改检测.
我创建了一个 4 分钟的截屏视频来解释 markForCheck() 和 detectChanges() - https://www.youtube.com/watch?v=OcphK_aEd7I 之间的区别