markForCheck()とdetectChanges()の違いは何ですか


174

違いは何であるChangeDetectorRef.markForCheck()とはChangeDetectorRef.detectChanges()

はSOに関する情報のみを見つけましたNgZone.run()が、これら2つの関数の違いはわかりません。

ドキュメントへの参照のみの回答の場合は、どちらかを選択する実際的なシナリオをいくつか示してください。



@Miladどうやって彼が反対票を投じたと思いますか?このサイトを閲覧する人はたくさんいます。
さようならStackExchange 2016

2
@FrankerZ、私が書いていて、反対票を見たので、1秒後に質問が更新され、「ドキュメントへの参照のみの回答の場合、どちらか一方を選択するいくつかの実際的なシナリオを示してください。それを明確にするのに役立ちます。私の心の中で」。
Milad、2016

3
反対票は、私がすでに見たドキュメントからコピーして貼り付けただけの元の回答を完了するようにあなたを奨励することでした。そしてそれはうまくいった!今答えは非常に明確になり、受け入れられた答えです、ありがとう
議会

3
@parliamentなんてだまし案だ!
HankCa 2017年

回答:


234

ドキュメントから:

detectChanges():void

変更検出器とその子をチェックします。

つまり、モデル(クラス)内の何かが変更されたが、ビューが反映されていない場合は、Angularにそれらの変更を検出(ローカルの変更を検出)してビューを更新するよう通知する必要があるかもしれません。

可能なシナリオは次のとおりです。

1-変更検出器がビューから切り離されます(detachを参照)

2-更新が行われましたが、Angularゾーン内にないため、Angularはそれを認識していません。

サードパーティの関数がモデルを更新し、その後ビューを更新したいときのように。

 someFunctionThatIsRunByAThirdPartyCode(){
     yourModel.text = "new text";
 }

このコードは(おそらく)Angularのゾーンの外にあるため、ほとんどの場合、変更を検出してビューを更新する必要があります。

 myFunction(){
   someFunctionThatIsRunByAThirdPartyCode();

   // Let's detect the changes that above function made to the model which Angular is not aware of.
    this.cd.detectChanges();
 }

上記の作業を行う方法は他にもあります。つまり、変更をAngularの変更サイクル内に取り込む方法は他にもあります。

**サードパーティの関数をzone.run内にラップできます。

 myFunction(){
   this.zone.run(this.someFunctionThatIsRunByAThirdPartyCode);
 }

** setTimeout内で関数をラップすることができます:

myFunction(){
   setTimeout(this.someFunctionThatIsRunByAThirdPartyCode,0);
 }

3- change detection cycle終了後にモデルを更新する場合もあります。その場合、この恐ろしいエラーが発生します。

「チェック後に式が変更されました」;

これは一般的に(Angular2言語から)を意味します:

受け入れられた方法(イベント、XHRリクエスト、setTimeoutなど)によって引き起こされたモデルの変更を確認し、変更検出を実行してビューを更新しましたが、それを終えましたが、別の方法がありましたモデルを再度更新したコード内の関数。AngularJSのようなダーティーチェックがなくなったので、変更検出を再度実行したくありません。Dでは、一方向のデータフローを使用する必要があります。

あなたは間違いなくこのエラーに出くわすでしょう:P。

それを修正する方法のカップル:

1- 適切な方法:更新が変更検出サイクル内にあることを確認します(Angular2の更新は一度だけ発生する一方向のフローであり、その後モデルを更新せず、コードをより適切な場所/時間に移動します)。

2- 遅延方法:更新後にdetectChanges()を実行してangular2を幸せにします。これは間違いなく最良の方法ではありませんが、考えられるシナリオを尋ねたところ、これはその1つです。

このようにあなたは言っています:変更検出を実行したことを心から知っていますが、チェックを完了した後にオンザフライで何かを更新しなければならなかったので、もう一度実行して欲しいのです。

3- ゾーンごとにパッチが適用され、終了後に実行されるsetTimeoutため、コードを内に配置します。setTimeoutdetectChanges


ドキュメントから

markForCheck() : void

すべてのChangeDetectionStrategy祖先をチェック対象としてマークします。

ときこれがほとんど必要とされているChangeDetectionStrategyコンポーネントのですOnPush

OnPush自体は、これらのいずれかが発生した場合にのみ変更検出を実行することを意味します。

1- @Inputプロパティの参照が完全に変更された場合、コンポーネントの@inputsの1つが新しい値で完全に置き換えられたか、単に入れられました。

もしそうならChangeDetectionStrategyコンポーネントのですOnPushそして、あなたが持っています:

   var obj = {
     name:'Milad'
   };

そして、あなたはそれを次のように更新/変更します:

  obj.name = "a new name";

これはobj参照を更新しないため、変更検出は実行されないため、ビューは更新/変更を反映していません。

この場合、ビューをチェックして更新するように手動でAngularに指示する必要があります(markForCheck)。

したがって、これを行った場合:

  obj.name = "a new name";

これを行う必要があります:

  this.cd.markForCheck();

むしろ、以下では変更検出が実行されます。

    obj = {
      name:"a new name"
    };

以前のobjを新しいに完全に置き換えました{}

2-クリックなどのイベントが発生したか、子コンポーネントのいずれかがイベントを発行しました。

次のようなイベント:

  • クリック
  • キーアップ
  • サブスクリプションイベント

要するに:

  • 使用detectChanges()角度がそれの変化検出を実行した後、またはアップデートは、すべての角度の世界にされていない場合は、モデルを更新しました。

  • 使用markForCheck()あなたはOnPushを使用していて、迂回している場合ChangeDetectionStrategy、一部のデータを変異したり、内部のモデルを更新したことでのsetTimeout


6
したがって、そのobjを変更した場合、ビューは更新されません。また、detectChangesを実行しても、変更がなかったため機能しません。これは正しくありません。detectChangesビューを更新します。この詳細な説明を参照してください。
Max Koretskyi

結論としてのmarkForCheckについても、正確ではありません。ここにある変形例から、この質問は、OnPushとmarkForCheckとオブジェクトの変更を検出しません。ただし OnPush戦略がない場合も同じ例が機能します。
Estus Flask 2017

@Maximus、あなたの最初のコメントに関して、私はあなたの投稿を読みました。しかし、あなたの説明では、戦略がOnPushであるかどうかthis.cdMode === ChangeDetectorStatus.Checkedは、ビューが更新されない場合を意味しています。そのため、markForCheckを使用します。
ミラード2017

そして、plunkerへのリンクに関しては、どちらの例も私にとってはうまく機能しています。私はあなたが何を意味しているのかわかりません
Milad

@Milad、それらのコメントは@estus :)からのものです。僕はについてだったdetectChanges。そしてcdModeAngular にはありません4.x.x。それについては私の記事に書いています。よかったです。メディアでおすすめするか、フォローしてください:)
Max Koretskyi 2017

99

2つの最大の違いは、detectChanges()実際には変更検出をトリガーしますが、変更検出markForCheck()はトリガーしません。

detectChanges

これは、トリガーしたコンポーネントで始まるコンポーネントのツリーの変更検出を実行するために使用されますdetectChanges()。したがって、変更検出は現在のコンポーネントとそのすべての子に対して実行されます。Angularはのルートコンポーネントツリーへの参照を保持し、ApplicationRef非同期操作が発生すると、ラッパーメソッドを通じてこのルートコンポーネントの変更検出をトリガーしますtick()

@Injectable()
export class ApplicationRef_ extends ApplicationRef {
  ...
  tick(): void {
    if (this._runningTick) {
      throw new Error('ApplicationRef.tick is called recursively');
    }

    const scope = ApplicationRef_._tickScope();
    try {
      this._runningTick = true;
      this._views.forEach((view) => view.detectChanges()); <------------------

viewこれがルートコンポーネントビューです。複数のコンポーネントをブートストラップすることの影響とはで説明したように、多くのルートコンポーネントが存在する可能性があります

@miladは、変更検出を手動でトリガーする必要がある可能性がある理由を説明しました。

markForCheck

先ほど述べたように、この人は変更検出をまったくトリガーしません。現在のコンポーネントからルートコンポーネントに上向きに移動し、ビューステートをに更新しますChecksEnabled。ここにソースコードがあります:

export function markParentViewsForCheck(view: ViewData) {
  let currView: ViewData|null = view;
  while (currView) {
    if (currView.def.flags & ViewFlags.OnPush) {
      currView.state |= ViewState.ChecksEnabled;  <-----------------
    }
    currView = currView.viewContainerParent || currView.parent;
  }
}

コンポーネントの実際の変更検出はスケジュールされていませんが、将来(現在または次のCDサイクルの一部として)発生すると、変更検出器が分離されていても、親コンポーネントビューがチェックされます。変更検出器は、変更検出戦略を使用するcd.detach()か、指定することにより、切り離すことができますOnPush。すべてのネイティブイベントハンドラーは、すべての親コンポーネントビューにチェックのマークを付けます。

このアプローチは、ngDoCheckライフサイクルフックでよく使用されます。詳しくは、コンポーネントがチェックされていることを意味する場合ngDoCheckこの記事をご覧ください

詳細については、Angularでの変更検出について知っておくべきことすべて参照してください。


1
コンポーネントと祖先でmarkForCheckを実行しているときに、detectChangesがコンポーネントとその子で機能するのはなぜですか?
パブロ2017

@pablo、それは仕様によるものです。私はその理論的根拠にはあまり詳しくありません
Max Koretskyi

@ AngularInDepth.comは、非常に集中的な処理がある場合、changedetectionがUIをブロックしますか?
alt255 2017年

1
@jerry、推奨されるアプローチは、内部的にサブスクリプションを追跡し、すべての新しい値トリガーで非同期パイプを使用することmarkForCheckです。したがって、非同期パイプを使用していない場合は、おそらくそれを使用する必要があります。ただし、変更の検出を開始するための非同期イベントの結果としてストアの更新が発生することに注意してください。それはほとんどの場合です。しかし、例外もあるblog.angularindepth.com/...
マックスKoretskyi

1
@MaxKoretskyiakaWizard返信ありがとうございます。はい、ストアの更新は主にフェッチまたはisFetching beforeの設定の結果です。そして、後..フェッチが、我々は常に使用することはできませんasync pipe我々は通常のように行うには、いくつかのものを持っているサブスクライブの内側にあるためcall setFromValues do some comparison...とあればasync、それ自体が呼び出してmarkForCheck、我々は自分自身それを呼び出す場合いただきました!問題は?しかし、繰り返しになりますが、通常ngOnInit、異なるデータを取得するセレクターは2〜3個、場合によってはそれ以上markForCheckあり、それらすべてを呼び出します。
ジェリー

0

cd.detectChanges() 現在のコンポーネントからその子孫まで、すぐに変更検出を実行します。

cd.markForCheck()変更検出を実行しませんが、祖先を変更検出を実行する必要があるとしてマークします。次に変更検出がどこでも実行されると、マークされたコンポーネントに対しても実行されます。

  • 変更検出が呼び出される回数を減らしたい場合は、useと呼ばれますcd.markForCheck()。多くの場合、変更は複数のコンポーネントに影響し、どこかで変更検出が呼び出されます。あなたは本質的に言っています:それが起こったときにこのコンポーネント更新されることを確認しましょう。(ビューは、私が書いたすべてのプロジェクトですぐに更新されますが、すべての単体テストでは更新されません)。
  • 現在、変更検出が実行されていないことが確実でない場合cd.detectChanges()は、を使用してください。その場合はエラーになります。これはおそらく、Angularの変更検出が設計されているという前提に反して動作している祖先コンポーネントの状態を編集しようとしていることを意味します。 cd.markForCheck()detectChanges()
  • 他のアクションの前にビューを同期的に更新することが重要な場合は、を使用しますdetectChanges()markForCheck()実際に時間内にビューを更新しない場合があります。何かを単体テストすると、ビューに影響します。たとえば、fixture.detectChanges()アプリ自体では不要な場合、手動で呼び出す必要があります。
  • 子孫よりも祖先が多いコンポーネントの状態を変更する場合はdetectChanges()、コンポーネントの祖先に対して不必要に変更検出を実行していないため、を使用するとパフォーマンスが向上する可能性があります。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.