角度の非同期パイプとパフォーマンス


角度async パイプは、アプリケーションのパフォーマンスを作るの礎石です.どのように正確に動作しますか?一緒にコードを歩いて理解しましょう.

あなたが角のアプリケーションとパフォーマンスについての記事を検索した場合は、約を読んでいるOnPush 変更検出.多くの人々がそれを使用して右にジャンプしますが、私はデータを操作し、それに依存するオブジェクトの突然変異を使用する傾向を参照してくださいDefault 変更検出戦略.通常、アプリケーションがオブジェクト突然変異の上に建設されるときOnPush 変更検出アプリケーションを中断します.通常、2つの解決策がありますDefault 変更検出戦略、他の1つを注入しているChangeDetectorRef を返します.markForCheck() コールバック関数の最後のメソッド.
使用Default これらの場合の変化検出戦略はパフォーマンスを増加させませんChangeDetectorRef すべてのコンポーネントには、面倒で不愉快なことができます.しかし、あなたはそれを避けることができますRxJS , とasync パイプ.

データ構成は重要です


私は、コンポーネントの中で観測可能な購読をして、結果をクラスメンバー特性に保存する現象に会いました.この構造にも詳しいでしょう.
// ...
data: Data[] = [];

constructor(private http: HttpClient) {}

ngOnInit(): void {
  this.http.get(`some/url`).subscribe(result => {
    this.data = result;
  })
}

コンポーネントクラスのデータプロパティに代入するのではなく、(そして私の謙虚な意見では)あなたのテンプレートのAsyncパイプを使用して、オブザーバブルを購読することができます.
{{ data$ | async }}
// ts

data$ = this.http.get(`some/url`);

どのように、asyncパイプは動きますか?


@Pipe({name: 'async', pure: false})
export class AsyncPipe implements OnDestroy, PipeTransform {
// ...
}
角度async パイプは純粋ではない.パイプが内部状態を持つたびにpure プロパティ@Pipe() デコレータの設定はfalseに設定する必要があります.つまり、transform() パイプの方法は、変更検出サイクル毎に呼び出される.以来async パイプは通常Observable or Promise 入力、パイプ自体は最後の値を格納するための内部状態を持っています.しかし、適切にティアダウンロジックを処理して、メモリリークを避けるためにSubscription , ソース_obj ) とSubscriptionStrategy メモリにも格納されます.
// ...
  private _latestValue: any = null;

  private _subscription: SubscriptionLike|Promise<any>|null = null;
  private _obj: Observable<any>|Promise<any>|EventEmitter<any>|null = null;
  private _strategy: SubscriptionStrategy = null!;

  constructor(private _ref: ChangeDetectorRef) {}

//...
ご覧の通り、ChangeDetectorRef 注入されるasync パイプインスタンスですが、あとであとで.まず、チェックしましょうSubscriptionStrategy インターフェイス.このインターフェイスを実装するクラスには、次のメソッドが必要です.createSubscription , dispose and onDestroy . 最初にサブスクリプションを作成し、DisposeとOnDestroyはtearDownロジックを処理する責任がありますので、メモリリークを回避できます.
interface SubscriptionStrategy {
  createSubscription(async: Observable<any>|Promise<any>, updateLatestValue: any): SubscriptionLike | Promise<any>;
  dispose(subscription: SubscriptionLike|Promise<any>): void;
  onDestroy(subscription: SubscriptionLike|Promise<any>): void;
}

class ObservableStrategy implements SubscriptionStrategy {
  createSubscription(async: Observable<any>, updateLatestValue: any): SubscriptionLike {
    return async.subscribe({
      next: updateLatestValue,
      error: (e: any) => {
        throw e;
      }
    });
  }

  dispose(subscription: SubscriptionLike): void {
    subscription.unsubscribe();
  }

  onDestroy(subscription: SubscriptionLike): void {
    subscription.unsubscribe();
  }
}

class PromiseStrategy implements SubscriptionStrategy {
  createSubscription(async: Promise<any>, updateLatestValue: (v: any) => any): Promise<any> {
    return async.then(updateLatestValue, e => {
      throw e;
    });
  }

  dispose(subscription: Promise<any>): void {}

  onDestroy(subscription: Promise<any>): void {}
}

const _promiseStrategy = new PromiseStrategy();
const _observableStrategy = new ObservableStrategy();

// ... Pipe class declaration
The ObservableStartegyPromiseStrategy クラスは処理する必要のあるロジックの周りのラッパです.一方dispose and onDestroy 約束取扱方法void 観測可能な戦略コール.unsubscribe() 両方の方法で.しかし、onDestroy メソッドは async_pipe.ts ファイルdispose メソッドは、取り消しを扱います.
@Pipe({name: 'async', pure: false})
export class AsyncPipe implements OnDestroy, PipeTransform {

// ...

  ngOnDestroy(): void {
    if (this._subscription) {
      this._dispose();
    }
  }

// ...

  private _dispose(): void {
    this._strategy.dispose(this._subscription!);
    this._latestValue = null;
    this._subscription = null;
    this._obj = null;
  }

// ...
}
このようにasync パイプはOnDestroy LifeCycleフック、インスタンスに格納されたサブスクリプションがある場合は、内部_dispose() メソッド.このメソッド呼び出しdispose 内部記憶について_strategy , を設定し、全てをNULLに設定します.これが発生すると、JSエンジンのガベージコレクターは残りを処理します.
// ...
  transform<T>(obj: null): null;
  transform<T>(obj: undefined): undefined;
  transform<T>(obj: Observable<T>|null|undefined): T|null;
  transform<T>(obj: Promise<T>|null|undefined): T|null;
  transform(obj: Observable<any>|Promise<any>|null|undefined): any {
    if (!this._obj) {
      if (obj) {
        this._subscribe(obj);
      }
      return this._latestValue;
    }

    if (obj !== this._obj) {
      this._dispose();
      return this.transform(obj as any);
    }

    return this._latestValue;
  }

// ...
The transform() メソッドは常に内部に_latestValue , したがって、いつでもasync パイプが使用され、最初の値は常にですnull . メソッドが呼び出され、指定されたパラメータがnull nor undefined , サブスクリプションが発生します.この内部_subscribe メソッドは、2、3のものを扱います.それはパイプのターゲットの参照を保存し、角度の内部を介して適切な戦略を選択しますɵisPromise and ɵisObservable ヘルパー機能.
  private _subscribe(obj: Observable<any>|Promise<any>|EventEmitter<any>): void {
    this._obj = obj;
    this._strategy = this._selectStrategy(obj);
    this._subscription = this._strategy.createSubscription(
        obj, (value: Object) => this._updateLatestValue(obj, value));
  }

  private _selectStrategy(obj: Observable<any>|Promise<any>|EventEmitter<any>): any {
    if (ɵisPromise(obj)) {
      return _promiseStrategy;
    }

    if (ɵisObservable(obj)) {
      return _observableStrategy;
    }

    throw invalidPipeArgumentError(AsyncPipe, obj);
  }

最後に、それはcreateSubscription 方法、内部を提供する_updateLatestValue コールバックメソッド.このメソッドは内部的に格納されているかどうかを調べますObservable そしてObservable は同じ参照で、同じ参照を持っています.もしあれば_latestValue が更新され、ChangeDetectorRef 's markForCheck() メソッドが呼び出され、購読時に変化検出をトリガーしますObservable 新しい値を出力します.これはrxjsとasync パイプハンドルOnPush 変更検出戦略.
  private _updateLatestValue(async: any, value: Object): void {
    if (async === this._obj) {
      this._latestValue = value;
      this._ref.markForCheck();
    }
  }
パイプのターゲットが新しいことができるのでObservable インスタンスも.以来Observables がオブジェクトである場合、それらは参照渡しされます.したがって、新しいプロパティをメンバープロパティに割り当てるたびにtransform メソッドは再帰的に実行されます.
// ...
  transform(obj: Observable<any>|Promise<any>|null|undefined): any {
    if (!this._obj) {
      if (obj) {
        this._subscribe(obj);
      }
      return this._latestValue;
    }

    if (obj !== this._obj) {
      this._dispose();
      return this.transform(obj as any);
    }

    return this._latestValue;
  }

// ...
既存のサブスクリプションがある場合は、内部に格納され、ターゲットに通知されますObservables がチェックされている場合は、参照によって異なる場合、古い(内部的に格納される)Observable を取得し、transform メソッドは新しいサブスクリプションを作成するために再帰的に呼び出されます.

実施例


つのタイマーでコンポーネントを作成しましょう.つのタイマは、2秒ごとに発行する必要があります、それは、非同期パイプを使用する必要があります、他の1つごとに2番目を発する必要がありますが、それはオブジェクトの突然変異を使用する必要があります.今のところ、デフォルトの変更検出戦略を使いましょう.
@Component({
  selector: 'app-test',
  template: `
  <div> Timer 1: {{ timer1$ | async }} </div>
  <div> Timer 2: {{ timer2 }} </div>
  `
})
export class TestComponent {
  timer1$ = timer(0, 2000);

  timer2 = 0

  constructor() {
    timer(0, 1000).subscribe((count) => {
      timer2 = count;
    })
  }
}
使用する場合Default 変更検出戦略、あなたはそれを見ることができますtimer2 1秒ごとに増加するtimer1$ asyncパイプは2秒毎に1増加します.さあスイッチしましょうOnPush 追加によって検出を変更changeDetection: ChangeDetectionStrategy.OnPush を返します.
現在timer2 バインディングは2秒ごとに2増加するtimer1$ 前と同じように動作します.つまり、2秒ごとに1増加します.なぜですかtimer2 増加するtimer1$ 発する?asyncパイプが検出を変更するので.あなたがコメントアウトするならば{{ timer$1 | async }} 一部のテンプレートからは、何も更新されることを観察することができます.

結論


使用async パイプとどのように動作するかを理解するより良い実行アプリケーションを書くことができます.あなたが使うときOnPush 変更の検出、角度は、より効率的に動作することができますので、オブジェクトの突然変異を監視する必要はありません.これらの場合はRxJS そして、データ合成は、反応性で実行可能なアプリケーションを作るのを援助することができます.