この記事では、Vueの非同期更新に関連する原理について説明します。この記事の中心的な理解は、キューという2つの単語です。
非同期更新
なぜ非同期更新が必要なのでしょうか?
考えてみてください。前の解説で説明したVueの反応性の原理に基づくと、反応性データは値の割り当て時にsetterが実行され、ビューの更新がトリガーされます。しかし、上記の例のように連続した代入のシナリオは非常に一般的です。ReactではsetState
内に書くことができますが、Vueにはそのようなメソッドはありません。
すべてのデータ更新ごとにページを即座にリフレッシュするわけにはいきません。各代入ごとにrender
、patch
を実行すると、ページ全体が非常に遅くなってしまいます。
この問題を解決するための鍵は、実行する必要がある更新関数をキューに保存し(また、キューに追加する際に同じ関数は重複して追加されません)、キューを非同期に実行することです。
関連する変数
Vueの非同期キューの原理を正式に分析する前に、使用されるこれらの変数を見てみましょう:
おそらく、waitingとflushingという非常に似た2つの変数がなぜ存在するのか疑問に思うかもしれません。
最初は私も疑問に思いました。flushing中かどうか、flushingを待っているか(waiting)か、微妙な違いがありますが:
- waiting中は非同期タスクが開始されていません
- flushing中はキュータスクが確定して開始されています
微妙な違いがありますが、それらのリセットタイミング(resetSchedulerState
)は同じです。2つの変数を分ける必要性はないように思えるので、一時的にコードをより自己説明的にするために2つの変数を使用しているのかもしれません。
キューに戻ると、このシステムには2つのキューが存在します。前の記事で説明したように、queueWatcher
は更新が必要なウォッチャーをキューに入れて、次回まとめて更新します。まずはウォッチャーのキューについて詳しく説明します。
queueWatcher
最後の nextTick(flushSchedulerQueue)
は、ウォッチャーキューを nextTickキューに入れるためのものです(引数はありませんが、flushSchedulerQueue
はqueueWatcher
が処理したqueue
を読み取ることができます)。
この短い関数呼び出しは、2つの重要な要素を結びつけています:flushSchedulerQueue
はウォッチャーキューを処理するための中核であり、nextTick
はVueの非同期レンダリングの中核です。
flushSchedulerQueue
公式のコメントによると、queue.sort
はキューをソートするために使用されます。これは次のことを保証するためです:
- 親コンポーネントが子コンポーネントよりも先に処理されること
- ユーザーが定義したウォッチャー関数がレンダリングウォッチャーよりも先に処理されること
- コンポーネントが親コンポーネントで破棄された場合、このウォッチャーをスキップすることができること(ウォッチャーのアクティブプロパティを使用)
簡単に言えば、flushSchedulerQueue
はqueueWatcher
で整理されたqueue
を処理するためのものです。
ここで、簡単な実践的な質問を提起します:ウォッチャーのコールバック関数内で最新のDOMにアクセスできますか?
答えは上記のコードに隠されています:flushSchedulerQueue
関数のqueue
のループ処理中、queue
はまだqueueWatcher
によってウォッチャーが追加されることがあります。これは主にユーザー定義のウォッチ関数やコンポーネント間の更新トリガーに使用されますが、次のループにスケジュールされずに直接queue
に挿入されます。
したがって、値の変化をウォッチし、コールバック関数内で最新のDOMを取得できると思わないでください。なぜなら、それらは同じタイミングで実行されるからです(また、コンポーネントのレンダリング関数は常に最後に実行されるためです)。
nextTick
まず、Vueが非同期実行を行うための方法を説明します。
まず、Vueが非同期実行を行うための方法を説明します。
まず、Vueが非同期実行を行うための方法を説明します。
まず、Vueが非同期実行を行うための方法を説明します。
非同期の遅延メカニズムです。バージョン2.4以前では、マイクロタスク(Promise/MutationObserver)を使用していましたが、マイクロタスクは実際には優先度が高すぎて、本来連続的に発生するイベントの間(例:#4521、#6690)や、同じイベントのバブリングの間(#6566)に挟まれて発生してしまいます。技術的には、setImmediateが理想的な選択肢ですが、どこでも利用できるわけではありません。また、同じループ内でトリガされたすべてのDOMイベントの後にコールバックをキューに入れる唯一のポリフィルは、MessageChannelを使用する方法です。
公式のコメントによると、最初のバージョンでは、VueはPromiseなどのマイクロタスクを使用して更新キューを作成していましたが、更新のタイミングが適切でないことがわかり、修正が行われました。
Vueでは、次の4つの方法で非同期呼び出しを作成できます:
- setImmediate
- MessageChannel(IE 10以降で使用可能で、Promiseよりも広範囲に使用できることに驚くかもしれません)
- Promise(Weex用に使用される)
- setTimeout 0(IE 10以下)
では、nextTick
は実際には何をしているのでしょうか?
コードを見ると、これは即時実行関数であり、queueNextTick
関数を返します。したがって、実際に使用しているのはqueueNextTick(cb, ctx)
です。(ただし、なぜここでcallbacks
やtimerFunc
などの変数をクロージャでラップして外部に直接置かないのかはよくわかりません)
nextTick(flushSchedulerQueue)
の場合、flushSchedulerQueue
はコールバック関数cbです。
- まず、cbはcallbacksにプッシュされます。これは非同期で実行されるキューであり、ウォッチャーキューとは異なります。
!pending
の条件に基づいて、pendingの場合はtimerFuncの実行をスキップし、前回のnextTickHandlerが完了するまでスキップします。
ちなみに、私たちが普段使っている$nextTick
は実際にはnextTick
関数と同じものです:
結論
2つのキュー:
- queueWatcherのキュー
- nextTickのcallbacks
queueは比較的自由で、コンポーネントの更新時に必要な変更を迅速に追加するのに便利です。
callbacks
はキューのように見えますが、実際には常に1つの関数がキューイングされている状態を保持しています(理論的にはそうであるはずですが、どのように検証するかわかりません。間違いがあれば指摘してください)
フロー:
- 非同期更新の目的は、更新関数の重複実行を防ぎ、スムーズな実行を実現することです。
- setterがウォッチャーをトリガーした後、コールバック関数は即座に実行されず、
queueWatcher
関数を介してウォッチャーがqueue
に追加されます。
- アイドル状態のとき、
queueWatcher
関数はnextTick(flushSchedulerQueue)
を実行し、queue
を次のタイマーで実行します。
flushSchedulerQueue
関数はウォッチャーのキューを実行します。
nextTick
は通常の$nextTick
と同じように、キューの実行を次のタイマーに遅延させるものです。
- Vueは4つの方法で次のタイマーを作成します:setImmediate、MessageChannel、Promise、setTimeout