ChangeDetectionStrategy.OnPush
その仕組みを理解しようとしています。
私の読書から収集したことは、変更検出は古い値と新しい値を比較することによって機能するということです。オブジェクト参照が変更されていない場合、その比較は false を返します。
ただし、その「ルール」がバイパスされる特定のシナリオがあるようです。それがどのように機能するのか説明していただけますか?
さて、これを理解するのに一晩かかったので、頭の中ですべてを解決するために履歴書を作成しました。これは将来の読者に役立つかもしれません. それでは、いくつかのことを片付けることから始めましょう。
コンポーネントにはフィールドがある場合があります。これらのフィールドは、何らかのイベントの後にのみ変更され、その後にのみ変更されます。
イベントは、マウス クリック、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;
}
これが、デフォルトの動作がすべてのコンポーネントをチェックする理由です。入力が変更されていない場合、サブコンポーネントは変更できませんが、角度は、入力が実際に変更されていないことを確認できないためです。渡されるオブジェクトは同じかもしれませんが、異なるプロパティを持つ可能性があります。
コンポーネントが でマークされている場合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 戦略では、次の場合にコンポーネントの変更検出が行われるためです。
戦略は問わない。
*ngFor
独自の変更検出を行います。変更検出が実行されるたびに、NgFor
そのngDoCheck()
メソッドが呼び出されNgFor
、配列の内容が変更されたかどうかがチェックされます。
あなたの場合、Angular がビューのレンダリングを開始する前にコンストラクターが実行されるため、変更はありません。
たとえば、次のようなボタンを追加する場合
<button (click)="persons.push({name: 'dynamically added', id: persons.length})">add</button>
クリックすると、実際ngFor
に認識しなければならない変更が発生します。
ChangeDetectionStrategy.OnPush
コンポーネントの変更検出が実行されるのは、OnPush
変更検出が実行されるときに実行されるためです。
(click)
@Input()
は変更検出によって更新されました| async
パイプがイベントを受け取りましたApplication.tick
changeDetector をデタッチしようとするのを防ぐには:
constructor(private cd: ChangeDetectorRef) {
ngAfterViewInit() {
this.cd.detach();
}