重构代码从ChangeDetectionStrategy.Default到ChangeDetectionStrategy.OnPush

时间:2019-02-11 19:00:02

标签: angular

最近几年我在React项目上度过了新的生活。

我有一个使用changeDetection: ChangeDetectionStrategy.OnPush的组件,我不喜欢我的解决方案。麻烦在于,我很难找到ChangeDetectionStrategy.OnPush

的任何真实示例

例如,我的组件有点像这样:

  files: Uploads[] = [];

  get canUpload() {
    return this.files.length > 0l
  }

  get isUploading() {
    return this.files.length > 0 && this.files.some((f) => f.state === FileUpLoadState.uploading);
  }

  get activeFiles() {
    return this.files.filter((f) => f.state !== FileUpLoadState.success);
  }

  uploadFiles() {
    if (!this.files.length) {
      return;
    }

    const fileList: FileList = (event.target as HTMLInputElement).files;

    for (const uploadedFile of Array.prototype.slice.call(fileList)) {
      // do stuff
      this.files.push(new Upload(file));
    }

  }

我具有像这样在模板中使用的这些属性;

 <button (click)="uploadFiles()" [disabled]="!this.canUpload">Upload</button>

我真的不喜欢这样,使用默认的更改检测将无法扩展,并且当更改传播到我的控制范围之外时。

如何重构此代码以使用OnPush更改检测?

4 个答案:

答案 0 :(得分:3)

根据下面的模板并使用ChangeDetectionStrategy.OnPushChangeDetection仅在单击按钮后运行。 (它仅对DOM事件实现)。其他所有内容均不会影响并且DetectChanges()函数不会运行(例如,从某些资源中获取某些数据不会影响您的代码)

import { ChangeDetectionStrategy } from '@angular/core';

@Component({
  selector: 'app-root',
   template: `
    <button (click)="uploadFiles()" [disabled]="!this.canUpload">Upload</button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  styleUrls: ['./app.component.css']
})

export class AppComponent {
    uploadFiles() {
      .../*Here's function body*/
    }
}

答案 1 :(得分:3)

人们 answer 您,但他们没有解释您。

OnPush策略是进行变更检测时最有效的策略。 Angular默认情况下不会实现它,因为新手常被用来查看魔术(即,当您开始使用Angular时,默认策略更易于出错且易于理解)。

要检测更改,Angular会监听您视图中的事件。使用默认策略,可能会导致很多无用的更改检测。

在推送策略中,您可以控制何时触发更改检测。

在两种情况下,Angular都使用内存引用来知道何时更新数据。这就是为什么对象不变性在Angular中如此重要的原因,也是反应式编程如此出色的原因。

话虽如此,如果您想切换到推送策略,则应使用以下内容:

  // files: Uploads[] = [];
  files: BehaviorSubject<Uploads[]> = new BehaviorSubject([]);

  add(item) {
    this.files.pipe(first()).subscribe(files => this.files.next([...files, item]));
  }

  get canUpload() {
    // return this.files.length > 0l
    return this.files.pipe(
      map(files => files.length),
      map(size => !!size)
    );
  }

  get isUploading() {
    // return this.files.length > 0 && this.files.some((f) => f.state === FileUpLoadState.uploading);
    return this.files.pipe(
      startWith(false),
      filter(files => !!files.length),
      filter(files => files.some(f => f.state === FileUpLoadState.uploading)),
      map(() => true)
    );
  }

  get activeFiles() {
    // return this.files.filter((f) => f.state !== FileUpLoadState.success);
    return this.files.pipe(
      map(files => files.filter(f => f.state !== FileUpLoadState.success)),
    );
  }

  uploadFiles() {
    /*
    if (!this.files.length) {
      return;
    }

    const fileList: FileList = (event.target as HTMLInputElement).files;

    for (const uploadedFile of Array.prototype.slice.call(fileList)) {
      // do stuff
      this.files.push(new Upload(file));
    }
    */
    this.files.pipe(
      filter(files => !!files.length),
      map(files => (event.target as HTMLInputElement).files),
      first()
    ).subscribe(files => {
      for (const uploadedFile of Array.prototype.slice.call(fileList)) {
        // do stuff
        this.add(new Upload(file));
      }
    });
  }

这是您可以执行的许多实现之一。我不确定这是否会按您期望的方式工作,我只是“翻译”了代码,因此您可能需要进行一两个调整。

有了它,您的视图将在集合更改时自动更新。您无需做任何事情。这符合推送策略,因为反应式编程会触发更改检测。

并且由于每个获取器都取决于您的BehaviorSubject,因此它们在该主题的每次更改时都会更新。

只是“缺点”(实际上不是),是在组件模板中必须使用异步管道:

 <ng-container *ngIf="canUpload | async">...</ng-container>

如果您有任何疑问,请随时提问!

答案 2 :(得分:0)

正如Dmitry S在his answer中所写的那样,我们可以将组件的ChangeDetectionStrategy设置为ChangeDetectionStrategy.OnPush

这告诉Angular该组件仅取决于其@inputs()(也称为纯),仅在以下情况下才需要检查:

  1. 输入参考更改。
  2. 一个事件源自该组件或其子组件。
  3. 我们明确运行更改检测。

Netanel Basal发布了a very helpful article on ChangeDetectionStrategy.OnPush

答案 3 :(得分:-1)

如果要完全控制变更检测,请使用OnPush-Strategy,获取一个changeDetectorReference

constructor(private changeDetector: ChangeDetectorRef){}

并在需要时触发更改检测,例如当数组更改或 您的上传方法。

uploadFiles() { 
// ...
this.changeDetector.detectChanges()
}