Angular2 ngFor OnPush 变化检测与数组突变

Angular2 ngFor OnPush Change Detection with Array Mutations

我有一个数据 table 组件 ( angular2-data-table ) 项目,我们将项目从 Angular 的传统变化检测更改为 OnPush 以优化渲染速度。

实施新的更改检测策略后,提交了一个引用 table 的错误,当数据对象发生变异时,例如对象的 属性 更新,则不会更新参考:https://github.com/swimlane/angular2-data-table/issues/255 .可以为这种类型的需求创建一个强大的用例,例如内联编辑或外部数据更改为大型数据集合(如股票代码)中的单个 属性。

为了解决这个问题,我们添加了一个名为 trackByProp 的自定义 trackBy 属性 检查器。参考:commit。不幸的是,这个解决方案没有解决问题。

在实时重新加载下的 demo page 上,您可以看到上面提交 运行 中引用的演示,但不会更新 table 直到您单击从而触发更改检测。

组件的结构是这样的:

Table > Body > Row Group > Row > Cell

所有这些组件都实现了OnPush。我在 setter 行中使用 getters/setters 来触发页面重新计算,如 here.

所示

我们希望为那些实施此模式的人保留 OnPush 更改检测,但是,作为一个有多个消费者的开源项目,人们可能会争论可见行的某种自定义检查功能屏幕上的值。

综上所述,trackBy 不会触发行单元格值中的更改检测,完成此操作的最佳方法是什么?

Angular2 变化检测不检查数组或对象的内容。

一个棘手的解决方法是在突变后创建数组的副本

this.myArray.push(newItem);
this.myArray = this.myArray.slice();

这样 this.myArray 引用不同的数组实例并且 Angular 将识别更改。

另一种方法是使用 IterableDiffer(对于数组)或 KeyValueDiffer(对于对象)

// inject a differ implementation 
constructor(differs: KeyValueDiffers) {
  // store the initial value to compare with
  this.differ = differs.find({}).create(null);
}

@Input() data: any;

ngDoCheck() {
  var changes = this.differ.diff(this.data); // check for changes
  if (changes && this.initialized) {
    // do something if changes were found
  }
}

另见 https://github.com/angular/angular/blob/14ee75924b6ae770115f7f260d720efa8bfb576a/modules/%40angular/common/src/directives/ng_class.ts#L122

您可能想使用 ChangeDetectorRef 中的 markForCheck 方法。


我确实有一个类似的问题,我确实有一个包含大量数据的组件并且在每个更改检测周期重新检查它们不是一个选项。但是当我们从 URL 观察一些属性并相应地更改视图中的内容时,onPush 我们的视图不会(自动)刷新。

因此在您的构造函数中,使用 DI 获取 changeDetectorRef 的实例: constructor(private changeDetectorRef: ChangeDetectorRef)

以及任何需要触发 changeDetection 的地方: this.changeDetectorRef.markForCheck();

我也遇到了类似的问题,要优化我的应用程序性能,我不得不使用 changeDetection.OnPush 策略。所以我将它注入到我的父组件和子组件的构造函数中,即 changeDetectorRef

的实例
    export class Parentcomponent{
       prop1;

       constructor(private _cd : ChangeDetectorRef){
          }
       makeXHRCall(){
        prop1 = ....something new value with new reference;
        this._cd.markForCheck(); // To force angular to trigger its change detection now
       }
    }

同样在子组件中,注入了changeDetectorRef

的实例
    export class ChildComponent{
     @Input myData: myData[];
     constructor(private _cd : ChangeDetectorRef){
          }
       changeInputVal(){
        this.myData = ....something new value with new reference;
        this._cd.markForCheck(); // To force angular to trigger its change detection now
       }
    }

Angular 每个异步函数都会触发更改检测:-

  • 任何 DOM 事件,如点击、提交、鼠标悬停。
  • 任何 XHR 调用
  • 任何定时器,如 setTimeout() 等

所以,这种方式会减慢应用程序的速度,因为即使我们拖动鼠标,angular 也会触发 changeDetection。对于跨越多个组件的复杂应用程序,这可能是一个主要的性能瓶颈,因为 angular 具有这种树状父子变化检测策略。 为避免这种情况,我们最好使用 OnPush 策略并强制触发 angular 的更改检测,我们看到有参考更改。

其次,在 OnPush 策略中,应该非常小心,它只会在对象引用发生变化时触发变化,而不仅仅是对象 属性 值,即 Angular 变化”意思是“新参考”。

例如:-

    obj = { a:'value1', b:'value2'}'
    obj.a = value3;

'a' in 'obj' 的 属性 值可能有变化,但 obj 仍然指向同一个引用,所以 Angular 变化检测不会在这里触发(除非你强迫它); 要创建一个新的引用,需要将该对象克隆到另一个对象中并相应地为其分配属性。

要进一步了解,请阅读不可变数据结构、变更检测 here

迟到的答案,但另一种解决方法是在突变后使用传播运算符。 myArr = [...myArr]myObj = {...myObj}

这甚至可以在变异时完成:myArr = myMutatingArr([...myArr]) 因为参数被作为数组的新引用,使变量成为新的引用,因此调用了 Angular 检查.

如前所述,如果您更改引用,将进行检查,在任何情况下都可以使用展开运算符来完成此操作。

请注意数据结构内部的嵌套数据结构需要将引用更改到嵌套级别。你将不得不做一个迭代 returns 内部传播,这样:

myObj = {...myObj, propToChange: { ...myObj.propToChange,
        nestedPropArr: [ ...myObj.propToChange.nestedPropArr ]
    }
}

如果您需要对对象等进行迭代,这可能会变得复杂。希望这对某人有所帮助!