【问题标题】:Why do we need `ngDoCheck`为什么我们需要`ngDoCheck`
【发布时间】:2017-07-27 08:54:52
【问题描述】:

我似乎无法弄清楚为什么我需要ngDoCheck 生命周期挂钩,而不是简单的通知,尤其是在其中编写代码如何在更改检测方面产生影响。我发现的大多数示例都显示了无用的示例,例如 this one,具有一堆日志记录功能。

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

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;
};

【问题讨论】:

    标签: angular angular2-docheck


    【解决方案1】:

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

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

    let obj = {
      iChange = 'hiii'
    }
    

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

    来自关于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();
           }
        }
    }
    

    【讨论】:

    • 谢谢,但我还是不明白。你的答案似乎是关于ngDoChanges。和ngDoCheck有什么关系?
    • @Maximus 如果您使用DoCheck,那么 PierreDuc 的含义,如果对象 ref 没有更改,则需要手动触发更改检测。
    • @echonax,这更令人困惑。也许你可以展示一个详细的例子?
    • @PierreDuc,你能看看this plunker吗?那里的计数器也没有更新
    • @echonax,发表了一篇关于更改检测的文章here,你可能想看看
    【解决方案2】:

    这篇很棒的文章If you think ngDoCheck means your component is being checked — read this article 深入解释了错误。

    此答案的内容基于 Angular 版本 2.x.x。有关最新版本 4.x.x,请参阅 this post

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

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

    当一个变更检测循环运行时,每个父视图都会检查是否应该对子视图here进行变更检测:

          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,因为this code

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

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

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

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

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

    查看完整示例 here in the sources,但这里是它的要点:

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

    第二个选项是在视图本身上调用detectChanges,如果cdMode 不是ChangeDetectorStatus.CheckedChangeDetectorStatus.Errored,则在当前视图上调用run change detection。由于 onPush angular 将 cdMode 设置为 ChangeDetectorStatus.CheckOnce,因此 angular 将运行更改检测。

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

    【讨论】:

      【解决方案3】:

      注意:

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

      ngOnChanges() 的限制

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

      ngDoCheck() ? 哇!

      检测深层变化,例如对象或项目中的属性变化被推入数组,即使没有参考变化。太棒了?

      【讨论】:

        【解决方案4】:

        简单来说:

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

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

        使用:

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

        【讨论】:

          猜你喜欢
          • 2019-06-09
          • 2014-06-18
          • 2017-02-26
          • 2011-04-03
          • 2020-09-21
          • 2020-03-09
          • 2018-12-24
          • 2012-04-08
          • 1970-01-01
          相关资源
          最近更新 更多