41

ChangeDetectionStrategy.OnPushその仕組みを理解しようとしています。

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

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

4

5 に答える 5

109

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

変化は出来事から生まれる

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

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

上から下へのデータフロー

Angular データ フローは一方通行です。つまり、データは子から親に流れません。@Inputたとえば、タグを介して親から子へのみ。子の何らかの変更を上位コンポーネントに認識させる唯一の方法は、イベントを使用することです。これにより、次のことがわかります。

イベントトリガー変化検出

イベントが発生すると、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

で true を返さずにオブジェクト プロパティが変更される可能性があるためchangeDetector isChanged()、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;
}

これが、デフォルトの動作がすべてのコンポーネントをチェックする理由です。入力が変更されていない場合、サブコンポーネントは変更できませんが、角度は、入力が実際に変更されていないことを確認できないためです。渡されるオブジェクトは同じかもしれませんが、異なるプロパティを持つ可能性があります。

OnPush 戦略

コンポーネントが でマークされている場合changeDetection: ChangeDetectionStrategy.OnPush、Angular は、オブジェクト参照が変更されていない場合、入力オブジェクトが変更されていないと想定します。つまり、プロパティを変更しても変更検出はトリガーされません。したがって、ビューはモデルと同期しなくなります。

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

プランク

@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 戦略でマークされていますが、ビューは変更されると更新されます..

プランク

@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 が変更されたように)
  • | | 非同期パイプがイベントを受け取りました
  • 変更検出は「手動で」呼び出されました

戦略は問わない。

リンク

于 2016-10-01T02:35:57.637 に答える
23

*ngFor独自の変更検出を行います。変更検出が実行されるたびに、NgForそのngDoCheck()メソッドが呼び出されNgFor、配列の内容が変更されたかどうかがチェックされます。

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

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

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

ChangeDetectionStrategy.OnPushコンポーネントの変更検出が実行されるのは、OnPush変更検出が実行されるときに実行されるためです。

  • バインドされたイベントを受け取りました(click)
  • an@Input()は変更検出によって更新されました
  • | asyncパイプがイベントを受け取りました
  • 変更検出は「手動で」呼び出されました
于 2016-09-30T16:03:57.590 に答える
7

Application.tickchangeDetector をデタッチしようとするのを防ぐには:

constructor(private cd: ChangeDetectorRef) {

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

プランカー

于 2016-09-30T16:06:41.657 に答える