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

14 浏览
0 Comments

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

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

我只在SO上找到了关于NgZone.run()的区别的信息,但没有找到这两个函数之间的区别。

对于那些只提供文档引用的答案,请举出一些实际场景以选择一个而不是另一个。

admin 更改状态以发布 2023年5月21日
0
0 Comments

两者之间最大的区别是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是根组件的视图。就像我在What are the implications of bootstrapping multiple components中所描述的那样,可能有多个根组件。

@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

0
0 Comments

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 中:

 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的祖先都标记为需要检查。

当组件的ChangeDetectionStrategyOnPush时,这通常是必需的。

OnPush意味着仅在发生以下情况之一时运行变更检测:

1-组件的@ inputs之一已被完全替换为新值,或者只需将@ 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-发生了某些事件,例如单击或像那样的某些东西或任何子组件发出了事件。

事件如:

  • 单击
  • KeyUp
  • 订阅事件
  • 等等。

所以简而言之:

  • 当您在Angular运行变更检测之后更新了模型或者更新根本不在Angular世界中时,请使用detectChanges()

  • 如果您正在使用OnPush,并且通过突变一些数据或在 setTimeout 中更新了模型来绕过 ChangeDetectionStrategy ,请使用markForCheck()

0