为什么我们需要`ngDoCheck`

Max*_*kyi 38 angular2-docheck angular

我似乎无法弄清楚为什么我需要ngDoCheck生命周期钩子而不是简单的通知,特别是如何在其中编写代码与变化检测有关.我发现的大多数示例都显示了无用的示例,如此示例,具有一堆日志记录功能.

此外,在生成的类中,我没有看到它被用于除简单通知之外的其他内容:

conmponent/wrapper.ngfactory.js

Wrapper_AppComponent.prototype.ngDoCheck = function(view,el,throwOnChange) {
  var self = this;
  var changed = self._changed;
  self._changed = false;
  if (!throwOnChange) {
    if (changed) {
      jit_setBindingDebugInfoForChanges1(view.renderer,el,self._changes);
      self._changes = {};
    }
    self.context.ngDoCheck(); <----------- this calls ngDoCheck on the component
                                           but the result is not used 
                                           anywhere and no params are passed
      }
      return changed;
    };
Run Code Online (Sandbox Code Playgroud)

Max*_*kyi 57

这篇伟大的文章如果您认为ngDoCheck您的组件正在被检查 - 请阅读本文深入解释错误.

此答案的内容基于角度版本2.xx对于最新版本4.xx,请参阅此帖子.

互联网上没有关于变更检测的内部工作的任何内容,所以我不得不花费大约一周的时间来调试源代码,因此这个答案在细节方面将非常具有技术性.

角度应用程序是一个视图树(AppView由编译器生成的Component特定类扩展的类).每个视图都有一个位于cdMode属性中的更改检测模式.cdModeis 的默认值ChangeDetectorStatus.CheckAlways,即cdMode = 2.

运行更改检测周期时,每个父视图都会检查是否应在此处对子视图执行更改检测:

  detectChanges(throwOnChange: boolean): void {
    const s = _scope_check(this.clazz);
    if (this.cdMode === ChangeDetectorStatus.Checked ||
        this.cdMode === ChangeDetectorStatus.Errored)
      return;
    if (this.cdMode === ChangeDetectorStatus.Destroyed) {
      this.throwDestroyedError('detectChanges');
    }
    this.detectChangesInternal(throwOnChange); <---- performs CD on child view
Run Code Online (Sandbox Code Playgroud)

在哪里this指向child视图.因此,如果cdModeChangeDetectorStatus.Checked=1,则由于此行而跳过针对直接子节点及其所有后代的更改检测.

if (this.cdMode === ChangeDetectorStatus.Checked ||
        this.cdMode === ChangeDetectorStatus.Errored)
      return;
Run Code Online (Sandbox Code Playgroud)

是什么changeDetection: ChangeDetectionStrategy.OnPush做的是简单地设置cdModeChangeDetectorStatus.CheckOnce = 0,所以变化检测后第一次运行子视图将其cdMode设置为ChangeDetectorStatus.Checked = 1的,因为这个代码:

if (this.cdMode === ChangeDetectorStatus.CheckOnce) 
     this.cdMode = ChangeDetectorStatus.Checked;
Run Code Online (Sandbox Code Playgroud)

这意味着下次更改检测周期开始时,不会对子视图执行更改检测.

如何为此类视图运行更改检测的选项很少.首先是将子视图更改cdModeChangeDetectorStatus.CheckOnce,这可以this._changeRef.markForCheck()ngDoCheck生命周期钩子中使用:

  constructor(private _changeRef: ChangeDetectorRef) {   }

  ngDoCheck() {
    this._changeRef.markForCheck();
  }
Run Code Online (Sandbox Code Playgroud)

这只是cdMode将当前视图及其父项ChangeDetectorStatus.CheckOnce更改为,因此下次执行更改检测时,将检查当前视图.

在源代码中查看完整的示例,但这里是它的要点:

      constructor(ref: ChangeDetectorRef) {
        setInterval(() => {
          this.numberOfTicks ++
          // the following is required, otherwise the view will not be updated
          this.ref.markForCheck();
          ^^^^^^^^^^^^^^^^^^^^^^^^
        }, 1000);
      }
Run Code Online (Sandbox Code Playgroud)

第二个选项是调用detectChanges视图本身,如果不是或者,它将在当前视图上运行更改检测.由于角度设置为,角度将运行变化检测.cdModeChangeDetectorStatus.CheckedChangeDetectorStatus.ErroredonPushcdModeChangeDetectorStatus.CheckOnce

因此ngDoCheck,不会覆盖已更改的检测,只需在每个更改的检测周期上调用它,并且唯一的工作就是将当前视图设置cdModecheckOnce,以便在下一个更改检测周期中检查更改.有关详情,请参阅此答案.如果当前视图的更改检测模式是checkAlways(如果未使用onPush策略,则默认设置),ngDoCheck似乎没有用.


Pie*_*Duc 11

DoCheck界面用于手动检测角度变化检测忽略的变化.可以在更改ChangeDetectionStrategy组件时使用,但是您知道对象的一个​​属性会发生变化.

检查这一个更改比让changeDetector运行整个组件更有效

let obj = {
  iChange: 'hiii'
}
Run Code Online (Sandbox Code Playgroud)

如果obj.iChange在模板内使用,如果此值发生更改,则angular将不会检测到它,因为其obj自身的引用不会更改.您需要实现一个ngDoCheck来检查值是否已更改,并调用detectChanges组件的changeDetector.

从角度文档来看 DoCheck

虽然ngDoCheck钩子可以检测到英雄的名字何时发生了变化,但是它的成本可怕.无论变化发生在何处,每个变化检测周期之后,都会以极大的频率调用此挂钩.在用户可以执行任何操作之前,在此示例中调用了20多次.

大多数这些初始检查是由Angular首次在页面上的其他位置呈现无关数据触发的.将鼠标移入另一个输入框会触发呼叫.相对较少的呼叫揭示了相关数据的实际变化.显然,我们的实现必须非常轻量级或者用户体验会受到影响.

测试的例子

@Component({
   selector: 'test-do-check',
   template: `
      <div [innerHtml]="obj.changer"></div>
   `, 
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TestDoCheckComponent implements DoCheck, OnInit {

    public obj: any = {
       changer: 1
    };

    private _oldValue: number = 1;

    constructor(private _changeRef: ChangeDetectorRef){}

    ngOnInit() {
       setInterval(() => {
           this.obj.changer += 1;
       }, 1000);
    }

    ngDoCheck() {
       if(this._oldValue !== this.obj.changer) {
           this._oldValue = this.obj.changer;

           //disable this line to see the counter not moving
           this._changeRef.detectChanges();
       }
    }

}
Run Code Online (Sandbox Code Playgroud)


Mab*_*abd 10

笔记:

用于更改检测的角度的默认算法通过引用比较输入绑定属性值来查找差异,请理解。凉爽的。

ngOnChanges() 的限制

由于角度变化检测的默认行为, ngOnChanges 无法检测是否有人更改了对象的属性或将项目推入 array 。所以ngDoCheck来回避。

ngDoCheck() 哇!

检测深度更改,例如对象或项目的属性更改被推送到数组中,即使没有引用更改。惊人的权利


小智 6

简单来说 :

一般在以下情况下进行组件检查:

  • 更新子组件输入绑定
  • 更新 DOM 插值
  • 更新查询列表

使用 :

深度观察会改变角度遗漏的部分。