ChangeDetectorRef.markForCheck()
和ChangeDetectorRef.detectChanges()
有什么区别?
我在SO上只找到了关于NgZone.run()
的区别,但没有关于这两个函数的区别。
对于仅提供文档引用的答案,请举例说明何时选择其中一个而不是另一个。
ChangeDetectorRef.markForCheck()
和ChangeDetectorRef.detectChanges()
有什么区别?
我在SO上只找到了关于NgZone.run()
的区别,但没有关于这两个函数的区别。
对于仅提供文档引用的答案,请举例说明何时选择其中一个而不是另一个。
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.run中:
myFunction(){
this.zone.run(this.someFunctionThatIsRunByAThirdPartyCode);
}
你可以将该函数封装在 setTimeout 中:
myFunction(){
setTimeout(this.someFunctionThatIsRunByAThirdPartyCode,0);
}
3- 还有一些情况是你在 变更检测周期
结束后更新了模型,这种情况下会出现以下错误:
"Expression has changed after it was checked";
这通常意味着(来自Angular2的语言):
我看到你的模型发生了一个变化,这个变化是由我的接受方式之一(事件,XHR请求,setTimeout等)引起的,然后我运行了变更检测以更新你的视图,并完成了它,但接着,你的代码中还有另一个函数更新了模型,我不想再次运行变更检测,因为没有像AngularJS一样的脏检查了:D,我们应该使用单向数据流!
你肯定会遇到这个错误:P。
解决它的几种方法:
1- 正确的方法:确保更新在变更检测周期内(Angular2的更新是单向流,仅发生一次,之后不再更新模型,并将代码移动到更好的位置/时间)。
2- 懒惰的方式: 在更新后运行 detectChanges() 让 Angular2 开心,这绝对不是最好的方法,但由于你询问可能的场景,这是其中之一。
通过这种方式,你在说:我真诚地知道你运行了变更检测,但我希望你再次运行它,因为我必须在检查完成后即时更新一些东西。
3- 将代码放入 setTimeout
中,因为 setTimeout
已被 zone 打补丁并将在结束后运行 detectChanges
。
来自文档的引述
markForCheck() : void
将所有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()
。
如果您正在使用 OnPush 并且通过改变某些数据或在 setTimeout 内更新模型来绕过 ChangeDetectionStrategy
,则使用 markForCheck()
。
两者最大的区别在于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解释了为什么你可能需要手动触发变更检测。
正如我所说,这个函数不会触发变更检测。它只是从当前组件向根组件上移,并将它们的视图状态更新为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`变更检测策略来分离。所有原生事件处理程序都会标记所有父组件视图以进行检查。markForCheck
。因此,如果您没有使用异步管道,那么这可能是您应该使用的方法。但是,请记住,存储更新应作为某些异步事件的结果发生,以启动更改检测。这通常是情况。但也有例外情况 https://blog.angularindepth.com/do-you-still-think-that-ngzone-zone-js-is-required-for-change-detection-in-angular-16f7a575afef#f618 - Max Koretskyiasync pipe
,因为在subscribe内部通常有一些事情要做,比如调用setFromValues
进行一些比较
。如果async
本身调用markForCheck
,那么如果我们自己调用它会有什么问题呢?但是,通常我们在ngOnInit
中有2-3个或者更多的选择器来获取不同的数据...并且我们在所有这些选择器中都调用了markForCheck
...这样可以吗? - jerry我制作了一个4分钟的屏幕录像,用于解释markForCheck()和detectChanges()之间的区别- https://www.youtube.com/watch?v=OcphK_aEd7I
cd.detectChanges()
会立即运行当前组件及其子组件的变更检测。
cd.markForCheck()
不会立即运行变更检测,但会标记其祖先组件需要运行变更检测。下一次任何地方执行变更检测时,也会为这些被标记的组件运行变更检测。
cd.markForCheck()
。通常情况下,更改会影响多个组件并且在某个地方会调用变更检测。你实际上是在说:当发生变更时,让我们确保也更新此组件。(在我编写的每个项目中,视图都会立即更新,但不是在每个单元测试中)。cd.detectChanges()
当前是否正在运行变更检测,请使用cd.markForCheck()
。在这种情况下,detectChanges()
将出错。这可能意味着你尝试编辑祖先组件的状态,这违反了Angular变更检测的设计假设。detectChanges()
。 markForCheck()
可能无法及时更新视图。例如,在单元测试中,如果对视图产生影响,则可能需要手动调用fixture.detectChanges()
,而在应用程序本身中则不需要。detectChanges()
来提高性能,因为您不会不必要地在组件的祖先上运行变更检测。最大的区别是markForCheck将检查当前组件的绑定,而不会调用子视图中的某些生命周期钩子(如DoCheck()),但detectChange()会!
title.name
的更改不会传播到子组件,即使使用markForCheck()也不行。 - Estus Flask