53

除了简单的通知之外,我似乎无法弄清楚为什么我需要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;
};
4

4 回答 4

90

这篇很棒的文章如果你认为ngDoCheck你的组件正在被检查——请阅读这篇文章,深入解释错误。

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

互联网上没有关于变更检测的内部工作原理,所以我不得不花大约一周的时间调试源代码,所以这个答案在细节上将是相当技术性的。

角度应用程序是视图树(AppView由编译器生成的组件特定类扩展的类)。每个视图都有一个存在于cdMode属性中的更改检测模式。的默认值为,cdMode即。ChangeDetectorStatus.CheckAlwayscdMode = 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

哪里this指向child视图。因此,如果cdModeChangeDetectorStatus.Checked=1,则由于这一行,直接子项及其所有后代的更改检测将被跳过。

    if (this.cdMode === ChangeDetectorStatus.Checked ||
            this.cdMode === ChangeDetectorStatus.Errored)
          return;

changeDetection: ChangeDetectionStrategy.OnPush所做的只是设置cdMode为,因此在第一次运行更改检测后,ChangeDetectorStatus.CheckOnce = 0子视图将cdMode设置为,ChangeDetectorStatus.Checked = 1因为此代码

    if (this.cdMode === ChangeDetectorStatus.CheckOnce) 
         this.cdMode = ChangeDetectorStatus.Checked;

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

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

      constructor(private _changeRef: ChangeDetectorRef) {   }
    
      ngDoCheck() {
        this._changeRef.markForCheck();
      }

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

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

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

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

所以ngDoCheck不会覆盖更改的检测,它只是在每个更改的检测周期中调用,唯一的工作是将当前视图设置cdModecheckOnce,以便在下一个更改检测周期中检查更改。有关详细信息,请参阅此答案。如果当前视图的变化检测模式是checkAlways(如果不使用onPush策略则默认设置),ngDoCheck似乎没什么用。

于 2017-03-15T10:38:47.817 回答
18

DoCheck接口用于手动检测角度变化检测忽略的变化。一种用途可能是您更改ChangeDetectionStrategy组件的属性,但您知道对象的一个​​属性会更改。

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

let obj = {
  iChange = 'hiii'
}

如果你obj.iChange在你的模板中使用,如果这个值发生变化,角度将不会检测到它,因为obj它本身的引用不会改变。您需要实现一个ngDoCheck来检查值是否已更改,并detectChanges在组件的 changeDetector 上调用 a。

从有关的角度文档DoCheck

虽然ngDoCheck钩子可以检测到英雄的名字何时改变,但它的代价是可怕的。这个钩子被非常频繁地调用——在每个更改检测周期之后,无论更改发生在哪里。在这个例子中,它在用户可以做任何事情之前被调用了二十多次。

大多数这些初始检查都是由 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();
       }
    }
}
于 2017-03-07T08:30:09.883 回答
5

笔记:

角度变化检测的默认算法通过参考比较输入绑定属性值来寻找差异,了解。凉爽的。

ngOnChanges() 的限制

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

ngDoCheck() 哇!

检测深度变化,例如对象或项目中的属性变化被推入数组,即使没有参考变化。惊人的权利

于 2021-07-23T03:47:04.907 回答
4

简单来说 :

通常在以下情况下进行组件检查:

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

采用 :

Deep Watch 改变了哪个角度错过了。

于 2019-12-16T21:26:31.913 回答