ChangeDetectionStrategy.OnPush 和 re-redering/domchanges

ChangeDetectionStrategy.OnPush and re-redering/domchanges

我试图找出我应该如何将 ngrx-store 与 OnPush 更改检测策略结合使用。

假设我想在 collection 中设置一个选定实体的 class。
如果我这样做:

 this.collection = Observable.combineLatest(
      this.store.let(fromStore.getCollection),
      this.store.let(fromStore.getSelected),
      (c, s) => c.map(entity => {return { ...entity, isSelected : s.id === entity.id ? true: 
 false }));

或者如果我在 reducer 中设置 isSelected 属性 它会创建我所有实体的克隆。大 collection 如果我这样做:

<div *ngFor="let entity of collection| async;let i = index;trackBy:entity?.id"
        [class.selected]="entity.isSelected">

速度很慢!

但是,如果我不订阅这样的选择更改:

this.collection = this.store.let(fromStore.getCollection);

改变

<div ... [class.selected]="entity.isSelected">

<div ...[class.selected]="isSelected(entity.id) | async">

并创建一个获取所选的函数:

  public isSelected(id): Observable<boolean> {
    return this.selected && this.selected.find(s => !!s.id === id));
  }

速度很快

所以看起来如果流改变使用流的组件将需要很多时间来检测domchanges,即使有none。

对吗?这意味着你必须非常清楚你在商店中更改了什么以及你应该在你的组件中做什么。

OnPush 策略与组件的模板表达式无关。它告诉 Angular 组件的所有 @Input() 绑定都是不可变的,并且组件的子组件是依赖的。

当 Angular 进行变化检测并到达具有 OnPush 的组件时,它将当前输入值与先前输入值进行比较。如果所有这些输入仍然 相等 ,则检测 在该组件处停止 。 None 个子项将被检查。

当 Angular 停止时 由于 OnPush 组件的视图及其子项未更新。输入绑定的不可变状态意味着视图没有改变。

如果您有可观察到的改变组件内部状态的 OnPush 策略。您必须在 ChangeDetectRef 上致电 markForCheck。这会标记该组件及其所有父组件以进行更改检测。

您所说的延迟只是您未正确使用 OnPush 时意外行为的一部分。

Angular 中有一篇关于变更检测的好文章:

https://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html

已更新:

OnPush 策略仅影响您的组件。它不会更改 ngForOf 指令或您在模板中使用的其他组件的行为。

lets say I have 200 items where 2 items are changed. I do an array.map and change the two items...

这是正确的策略。而不是修改现有数组和 replace/remove 项。最好生成一个新数组,以便 Angular 可以快速看到它已更改。

ngForOf 将在每次 必须进行变化检测时迭代数组中的所有项目 。对于每个项目,它将按函数调用跟踪,或者将哈希值附加到项目。这就是它告诉数组中的项目是否已更改的方式。它们可以通过移除或重新排序进行更改。

ngForOf 为数组中的每个项目维护一个 view。它将迭代每个项目,然后对每个视图执行更改检测。

如果数组引用保持不变,或者更改为新数组,

ngForOf 执行相同的工作量。它仍然必须迭代每个项目。 ngForOf无法解析的是哈希值何时消失。它必须去除旧视图并为每个项目创建一个新的 DOM 视图。

让我们看看你的例子:

<div *ngFor="let entity of collection| async;let i = index;trackBy:entity?.id" [class.selected]="entity.isSelected">

您正在将 trackBy 与此表达式 entity?.id 一起使用。该表达式有几个问题。

  • 必须给 trackBy 一个函数引用。这是从 Angular 1 更改为缩小代码的要求。
  • 当实体值不存在时,表达式 entity? 产生 undefinedngForOf 无法追踪 undefined,将被迫为每个未定义的项目拆下并重建 DOM。
  • ngForOf 无法跟踪 entity?.id 除非 属性 id 是函数。我认为这是一个 属性 值,ngForOf 将忽略它。
  • 最后,我认为 entity 变量不存在于 ngForOf 表达式的范围内。所以 trackBy 不会工作。

这意味着每次重新创建数组时,DOM 都会重新构建,这会非常慢。

您需要在组件中使用有效的跟踪功能:

 public trackEntity(indx: number, value: any) {
     if('id' in value) {
           return value.id;
     }
     return value || indx;
 }

以上将尝试通过实体 ID 属性 进行跟踪,但如果您有 undefined 项,这将回退到索引偏移量。

在您的模板中,您需要为 trackBy:

使用上述函数
<div *ngFor="let entity of collection| async;let i = index;trackBy:trackEntity" [class.selected]="entity.isSelected">

请注意没有 () 大括号。该函数正在通过引用传递。