Angular開発実践(五):変化モニタリングを深く解析する


変化モニタリングとは


Angularを使用した開発では、モデルからビューへの入力バインディング、ビューからモデルへの出力バインディング、およびビューとモデルの双方向バインディングなど、Angularへのバインディングによく使用されます.これらのバインド値がビューとモデルの間で同期を保つことができるのは,Angularにおける変化検出のおかげである.
簡単に言えば、変化検出とは、Angularがビューとモデルの間のバインド値が変化したかどうかを検出するために使用し、モデル内のバインド値が変化したことを検出すると、ビューに同期し、逆に、ビュー上のバインド値が変化したことを検出すると、対応するバインド関数がコールバックされる.

変化モニタリングのソース


変化モニタリングの鍵は、バインドされた値が変化したかどうかをどのように最小粒度でモニタリングするかであり、どのような場合にこれらのバインドされた値が変化するのでしょうか.私たちがよく使ういくつかのシーンを見てみましょう.

Events: click/hover/...

@Component({
  selector: 'demo-component',
  template: `
    

{{name}}

` }) export class DemoComponent { name: string = 'Tom'; changeName() { this.name = 'Jerry'; } }

テンプレートでは、補間式によってnameプロパティをバインドします.change name をクリックするとname属性の値が変更され、テンプレートビュー表示内容も変更される.

XHR/webSocket

@Component({
  selector: 'demo-component',
  template: `
    

{{name}}

` }) export class DemoComponent implements OnInit { name: string = 'Tom'; constructor(public http: HttpClient) {} ngOnInit() { // ./getNewName , 'Jerry' this.http.get('./getNewName').subscribe((data: string) => { this.name = data; }); } }

このコンポーネントのngOnInit関数でサーバ側にAjaxリクエストを送信しました.このリクエストが結果を返すと、現在のテンプレートビューでバインドされているname属性の値も変更されます.

Times: setTimeout/requestAnimationFrame

@Component({
  selector: 'demo-component',
  template: `
    

{{name}}

` }) export class DemoComponent implements OnInit { name: string = 'Tom'; constructor() {} ngOnInit() { // ./getNewName , 'Jerry' setTimeout(() => { this.name = 'Jerry'; }, 1000); } }

このコンポーネントのngOnInit関数では、タイミングタスクを設定することで、タイミングタスクが実行されると、現在のビューにバインドされているname属性の値も変更されます.

まとめ

  • 実際には、バインド値の変更をもたらすこれらのイベントが非同期で発生しているという共通点が上記の3つの状況にあることがわかります.
  • Angularは、オブジェクトの変動をキャプチャするものではなく、適切なタイミングでオブジェクトの値が変更されたかどうかを検証するものであり、このタイミングがこれらの非同期イベントの発生である.
  • このタイミングはNgZoneというサービスによって制御され、アプリケーション全体の実行コンテキストを取得し、関連する非同期イベントの発生、完了、または異常などをキャプチャし、Angularの変化監視メカニズムを駆動して実行することができる.

  • 変化モニタリングの処理メカニズム


    以上より,変化検出がどのようにトリガされるかを大まかに理解したが,Angularにおける変化モニタリングはどのように行われるのか.
    まず、各コンポーネントに対して、対応する変化モニタがあることを知る必要があります.すなわち,各ComponentにはchangeDetectorが対応しており,Componentでは依存注入によりchangeDetectorを得ることができる.
    一方,我々の複数のComponentは木構造の組織であり,1つのComponentがchangeDetectorに対応するため,changeDetector間は同様に木構造の組織である.
    最後に覚えておきたいのは、変化モニタリングのたびにComponentの木の根から始まることです.

    例を挙げる


    サブアセンブリ:
    @Component({
      selector: 'demo-child',
      template: `
        

    {{title}}

    {{paramOne}}

    {{paramTwo}}

    ` }) export class DemoChildComponent { title: string = ' '; @Input() paramOne: any; // 1 @Input() paramTwo: any; // 2 }

    親コンポーネント:
    @Component({
      selector: 'demo-parent',
      template: `
        

    {{title}}

    ` }) export class DemoParentComponent { title: string = ' '; paramOneVal: any = ' paramOne '; paramTwoVal: any = ' paramTwo '; changeVal() { this.paramOneVal = ' paramOne '; } }

    上のコードでは、DemoParentComponentはDemoChildComponentにラベルで埋め込まれており、ツリー構造上、DemoParentComponentはDemoChildComponentのルートノードであり、DemoChildComponentはDemoParentComponentのリーフノードである.
    DemoParentComponentのbuttonをクリックすると、changeValメソッドにコールバックされ、変更モニタリングの実行がトリガーされます.変更モニタリングの流れは次のとおりです.
    まず、変化検出はDemoParentComponentから開始します.
  • title値が変化するか否かを検出:変化なし
  • paramOneVal値が変化するかどうかを検出する:変化が発生した(ボタン呼び出しchangeVal()メソッドが変化した)
  • .
  • paramTwoVal値が変化したかどうかを検出する:変化なし
  • その後、変化検出はリーフノードDemoChildComponentに入ります.
  • title値が変化するか否かを検出:変化なし
  • paramOneが変化したかどうかを検出する:変化した(親コンポーネントの属性paramOneValが変化したため)
  • paramTwoが変化したかどうかを検出する:変化なし
  • 最後に、DemoChildComponentにはリーフノードがないため、変化モニタリングはDOMを更新し、ビューとモデルの間の変化を同期します.

    変化モニタリングポリシー


    変化モニタリングの処理メカニズムを学んだ後、このメカニズムは少し簡単で乱暴ではないかと思うかもしれません.もし私のアプリケーションに何百人ものComponentがあり、任意のComponentがモニタリングをトリガーしたら、ルートノードからリーフノードまで再検出する必要があります.
    焦らないで、Angularの開発チームはすでにこの問題を考慮して、上述の検出メカニズムはただ1種のデフォルトの検出メカニズムで、Angularはまた1種のOnPushの検出メカニズム(メタデータ属性changeDetection:ChangeDetectionStrategy.OnPushを設定する)を提供します.
    OnPushとDefaultの違い:サブコンポーネント入力にバインドされた値が変化していないことが検出された場合、変化検出はサブコンポーネントに深く入り込まない.

    変更モニタクラス-ChangeDetectorRef


    前述したように、コンポーネントメタデータ属性changeDetectionを変更して、コンポーネントの変化モニタリングポリシー(ChangeDetectionStrategy.DefaultまたはChangeDetectionStrategy.OnPush)を変更することができます.また、ChangeDetectorRefを使用して、コンポーネントの変化モニタリングをより柔軟に制御することもできます.
    Angularは、実行中に各コンポーネントに対してChangeDetectorRefのインスタンスを作成します.このインスタンスは、変更モニタリングを手動で管理する方法を提供します.このクラスでは、変更モニタリングの停止/有効化、または指定されたパスによる変更モニタリングなど、コンポーネントの変更モニタリングポリシーをカスタマイズできます.
    関連方法は次のとおりです.
  • markForCheck():ルートコンポーネントとそのコンポーネントの間のこのパスをマークし、Angularに次回変更モニタリングをトリガーするときにこのパス上のコンポーネントをチェックしなければならないことを通知します.
  • detach():変更モニタツリーから変更モニタを分離します.このコンポーネントの変更モニタは、reattach()メソッドを再び手動で実行しない限り、変更モニタを実行しません.
  • reattach():分離された変化モニタを再インストールし、コンポーネントとそのサブコンポーネントが変化モニタリングを実行できるようにします.
  • detectChanges():各サブコンポーネントへの変更モニタリングの実行を手動でトリガーします.

  • 使用方法も簡単で、コンポーネントに直接注入すればいいです.
    @Component({
      selector: 'demo-parent',
      template: `
        

    {{title}}

    ` }) export class DemoParentComponent implements OnInit { title: string = ' '; constructor(public cdRef: ChangeDetectorRef) {} ngOnInit() { this.cdRef.detach(); // , } }