web-dev-qa-db-ja.com

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

ChangeDetectorRef.markForCheck()ChangeDetectorRef.detectChanges()の違いは何ですか?

私は SO についての情報をNgZone.run()の間の違いに関して見つけることができましたが、これら2つの関数の間では見つけませんでした。

ドキュメントを参照するだけの回答については、実際のシナリオをいくつか選択してください。

124
parliament

ドキュメントから:

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();
 }

NOTE

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

**あなたはzone.runの中にそのサードパーティの関数をラップすることができます:

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

**あなたはsetTimeoutの中に関数をラップすることができます:

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

3- change detection cycleが終了した後にモデルを更新する場合もあります、その場合あなたはこの恐ろしいエラーに遭遇します:

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

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

モデルの変更が、私が受け入れている方法(イベント、XHRリクエスト、setTimeoutなど)の1つによって引き起こされているのがわかりました。次に、変更検出を実行してビューを更新しましたが、終了しました。モデルを更新したあなたのコードの中の関数で、AngularJSのようなダーティチェックがもうないので、私は再び変更検出を実行したくないでしょう:Dそして一方向のデータフローを使うべきです!

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

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

1-正しい方法:更新が変更検出サイクル内にあることを確認します(Angular 2の更新は1回発生する一方向のフローで、その後モデルを更新せずにコードをより良い場所/時間)。

2-怠惰な方法:その更新の後にdetectChanges()を実行してangle2を幸せにします。これは絶対に最善の方法ではありませんが、考えられるシナリオは何ですか?そのうちの。

このようにあなたは言っています:私は誠意をこめて変更の検出を実行したことを知っています、しかし私はあなたがチェックを終えた後その場で何かを更新しなければならなかった.

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


ドキュメントから

markForCheck() : void

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

これは、コンポーネントのChangeDetectionStrategyOnPushの場合に主に必要です。

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

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

あなたのコンポーネントのChangeDetectionStrategyOnPushであるなら、あなたは以下のようになります。

   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-クリックなどのイベントが発生したか、そのようなもの、または任意の子コンポーネントがイベントを発行した。

ようなイベント:

  • クリック
  • キーアップ
  • 購読イベント
  • 等.

要するに、

  • Angularが実行された後にモデルを更新したとき、または更新がAngular Worldで行われていない場合は、detectChanges()を使用します。

  • OnPushを使用していて、データを変更してChangeDetectionStrategyを迂回する場合、またはsetTimeout内でモデルを更新した場合は、markForCheck()を使用します。

172
Milad

両者の最大の違いは、detectChanges()が実際には変更検出をトリガーするのに対し、markForCheck()は変更検出をトリガーしないことです。

detectChanges

これは、detectChanges() onをトリガーしたコンポーネントから始めて、コンポーネントのツリーに対する変更検出を実行するために使用されます。そのため、変更検出は現在のコンポーネントとそのすべての子に対して実行されます。 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 で変更検出について知っておく必要があるすべてを参照してください。