web-dev-qa-db-ja.com

angular 2変更検出およびChangeDetectionStrategy.OnPush

ChangeDetectionStrategy.OnPushメカニズムを理解しようとしています。

読み取りから収集したのは、古い値を新しい値と比較することにより、変化の検出が機能するということです。オブジェクト参照が変更されていない場合、その比較はfalseを返します。

ただし、その「ルール」がバイパスされる特定のシナリオがあるようです。どのように機能するのか説明していただけますか?

27
Ced

さて、これは私の頭の中のすべてを解決するために履歴書を作成したことを理解するのに一晩かかったので、将来の読者に役立つかもしれません。それでは、いくつかのことを片付けることから始めましょう。

イベントからの変化

コンポーネントにはフィールドがあります。これらのフィールドは、ある種のイベントの後、そしてその後のみ変更されます。

マウスクリック、ajaxリクエスト、setTimeout ...としてイベントを定義できます。

データは上から下に流れます

角度データフローは一方通行です。つまり、データは子供から親に流れません。たとえば、_@Input_タグを介した親から子へのみ。上位コンポーネントに子の変更を認識させる唯一の方法は、eventを使用することです。これにより、次のことが可能になります。

イベントトリガーの変更検出

イベントが発生すると、angularフレームワークは、すべてのコンポーネントを上から下にチェックして、変更されているかどうかを確認します。変更があれば、それに応じてビューを更新します。

Angularは、イベントが発生した後にすべてのコンポーネントをチェックします。最下位レベルのコンポーネントであるコンポーネントでクリックイベントが発生したとします。つまり、コンポーネントには親はあるが子はありません。そのクリックは、イベントエミッタ、サービスなどを介して親コンポーネントの変更をトリガーする可能性があります。Angularは、親が変更されるかどうかわからない。そのためAngularは、デフォルトでイベントが発生した後にすべてのコンポーネントをチェックします。

それらが変更されたかどうかを確認するには、angularを使用してChangeDetectorクラスを使用します。

変更検出器

すべてのコンポーネントには、変更検出クラスが添付されています。何らかのイベント後にコンポーネントの状態が変化したかどうかを確認し、ビューを更新する必要があるかどうかを確認するために使用されます。イベントが発生すると(マウスクリックなど)、この変更検出プロセスはすべてのコンポーネントで(デフォルトで)発生します。

たとえば、ParentComponentがある場合:

_@Component({
  selector: 'comp-parent',
  template:'<comp-child [name]="name"></comp-child>'
})
class ParentComponent{
  name:string;
} 
_

次のようなParentComponentに変更検出器を接続します。

_class ParentComponentChangeDetector{
    oldName:string; // saves the old state of the component.

    isChanged(newName){
      if(this.oldName !== newName)
          return true;
      else
          return false;
    }
}
_

オブジェクトのプロパティを変更する

お気づきかもしれませんが、オブジェクトプロパティを変更すると、isChangedメソッドはfalseを返します。確かに

_let prop = {name:"cat"};
let oldProp = prop;
//change prop
prop.name = "dog";
oldProp === prop; //true
_

changeDetectorisChanged()でtrueを返さずにオブジェクトプロパティを変更できる場合、angularは以下のすべてのコンポーネントも変更されたと想定します。すべてのコンポーネントの変更検出をチェックするだけです。

例:ここにはサブコンポーネントを持つコンポーネントがあります。変更検出は親コンポーネントに対してfalseを返しますが、子のビューは非常に適切に更新される必要があります。

_@Component({
  selector: 'parent-comp',
  template: `
    <div class="orange" (click)="person.name='frank'">
      <sub-comp [person]="person"></sub-comp>
    </div>
  `
})
export class ParentComponent {
  person:Person = { name: "thierry" };     
}

// sub component
@Component({
  selector: 'sub-comp',
  template: `
    <div>
      {{person.name}}
    </div>
})
export class SubComponent{
  @Input("person") 
  person:Person;
}
_

そのため、デフォルトの動作ではすべてのコンポーネントをチェックします。サブコンポーネントは入力が変更されていなければ変更できないにもかかわらず、angularは入力が変更されていないことを確実に知らない本当にが変更されたためです。それは同じかもしれませんが、異なるプロパティを持つことができます。

OnPush戦略

コンポーネントが_changeDetection: ChangeDetectionStrategy.OnPush_でマークされている場合、angularは、オブジェクト参照が変更されなかった場合、入力オブジェクトが変更されなかったと想定します。したがって、ビューはモデルと同期しなくなります。

この例は、実際にこれを示しているため、素晴らしいものです。クリックすると、入力オブジェクト名のプロパティが変更される親コンポーネントがあります。親コンポーネント内のclick()メソッドをチェックすると、コンソールで子コンポーネントプロパティが出力されることに気付くでしょう。そのプロパティは変更されました。しかし、視覚的に見ることはできません。これは、ビューが更新されていないためです。 OnPush戦略により、refオブジェクトが変更されなかったため、変更検出プロセスは発生しませんでした。

Plnkr

_@Component({
  selector: 'my-app',
  template: `
    <div class="orange" (click)="click()">
      <sub-comp [person]="person" #sub></sub-comp>
    </div>
  `
})
export class App {
  person:Person = { name: "thierry" };
  @ViewChild("sub") sub;

  click(){
    this.person.name = "Jean";
    console.log(this.sub.person);
  }
}

// sub component
@Component({
  selector: 'sub-comp',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div>
      {{person.name}}
    </div>
  `
})
export class SubComponent{
  @Input("person") 
  person:Person;
}

export interface Person{
  name:string,
}
_

クリック後、名前はビューではまだティエリーですが、コンポーネント自体ではありません


コンポーネント内で発生したイベントは、変更検出をトリガーします。

ここで、元の質問で私を混乱させたものに至ります。以下のコンポーネントはOnPush戦略でマークされていますが、ビューは変更されると更新されます。

Plnkr

_@Component({
  selector: 'my-app',
  template: `
    <div class="orange" >
      <sub-comp ></sub-comp>
    </div>
  `,
  styles:[`
    .orange{ background:orange; width:250px; height:250px;}
  `]
})
export class App {
  person:Person = { name: "thierry" };      
  click(){
    this.person.name = "Jean";
    console.log(this.sub.person);
  }

}

// sub component
@Component({
  selector: 'sub-comp',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="grey" (click)="click()">
      {{person.name}}
    </div>
  `,
  styles:[`
    .grey{ background:#ccc; width:100px; height:100px;}
  `]
})
export class SubComponent{
  @Input()
  person:Person = { name:"jhon" };
  click(){
    this.person.name = "mich";
  }
}
_

そのため、ここでは、オブジェクト入力が参照を変更していないことがわかり、戦略OnPushを使用しています。そのため、更新されないと思われる可能性があります。実際には更新されます。

Gunterが答えで言ったように、それは、OnPush戦略では、次の場合にコンポーネントの変更検出が発生するためです。

  • コンポーネント自体でバインドされたイベントを受信(クリック)します。
  • @Input()が更新されました(ref objの変更のように)
  • |非同期パイプがイベントを受信しました
  • 変更検出が「手動で」呼び出された

戦略に関係なく。

リンク集

85
Ced

_*ngFor_は、独自の変更検出を行います。変更検出が実行されるたびに、NgForngDoCheck()メソッドを呼び出し、NgForが配列の内容が変更されたかどうかを確認します。

あなたの場合、コンストラクタはAngularがビューのレンダリングを開始する前に実行されるため、変更はありません。
たとえば、次のようなボタンを追加する場合

_<button (click)="persons.Push({name: 'dynamically added', id: persons.length})">add</button>
_

クリックすると、実際にngForが認識しなければならない変更が発生します。

_ChangeDetectionStrategy.OnPush_を使用すると、OnPushを使用すると変更検出が実行されるため、コンポーネントの変更検出が実行されます。

  • バインドされたイベントを受信します_(click)_
  • @Input()が変更検出によって更新されました
  • _| async_パイプがイベントを受け取りました
  • 変更検出が「手動で」呼び出された
19

Application.tickを防ぐには、changeDetectorをデタッチします。

constructor(private cd: ChangeDetectorRef) {

ngAfterViewInit() {
  this.cd.detach();
}

Plunker

7
yurzui

In angular Parent-child structureを使用します。そこで、@ Inputsを使用してData form parentを子に渡します。

そこで、子の祖先で変更が発生すると、その祖先からのコンポーネントツリーで変更の検出が行われます。

しかし、ほとんどの状況では、入力が変更された場合にのみ、子のビューを更新する必要があります(Change Detectionを呼び出します)。これを実現するには、OnPushChangeDetectionStrategyを使用して、入力を変更します(使用不変)必要に応じて。 [〜#〜] link [〜#〜]

1