キーワード: vue $nextTick、非同期更新キュー、マイクロタスク、マクロタスク、EventLoop
目次
- Vueとは$nextTick
- $nextTick
- ソースコード解析
- 雑多な情報
Vueとは$nextTick
Vueは、DOMを更新するときに非同期で実行します。データ変更が発生すると、Vue はキューを開き、同じイベントループ内で発生したすべてのデータ変更をバッファリングします。同じウォッチャーが複数回トリガーされても、キューにプッシュされるのは一度だけです。このようにバッファから重複を取り除くことは、不要な計算やDOM操作を避けるために重要です。次に、イベントループの次の "tick "で、Vueはキューを更新し、実際の処理を実行します。内部的には、VueはネイティブのPromise.then、MutationObserver、非同期キュー用のsetImmediateを、環境がサポートしていない場合は、非同期キュー用のsetTimeout()を使用しようとします。そして、MutationObserverとsetImmediateは非同期キューに対して内部的に試行され、実行環境がそれらをサポートしていない場合、代わりにsetTimeout(fn, 0)が使用されます。
$nextTick
- ビューが更新された後、新しいビューに基づいて操作する必要があります。
- created()フックが実行されている間、DOMは実際にはレンダリングを行わないため、これはcreatedサイクルの間に使用され、実際にはmounted()フックと同等です。
- 使用法 1:
<div id="example">{{message}}</div>
var vm = new Vue({
el: '#example',
data: {
message: '123'
}
})
vm.message = 'new message' // データを変更する
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
vm.$el.textContent === 'new message' // true
})
- コンポーネント内でvm.$nextTick()インスタンスメソッドを使用すると、グローバルなVueを必要とせず、コールバック関数内の thisが自動的に現在のVueインスタンスにバインドされるため、特に便利です:
Vue.component('example', {
template: '<span>{{ message }}</span>',
data: function () {
return {
message: ' '
}
},
methods: {
updateMessage: function () {
this.message = '更新'
console.log(this.$el.textContent) // => ' '
this.$nextTick(function () {
console.log(this.$el.textContent) // => '更新'
})
}
}
})
- 使用法2、$nextTick()はPromiseオブジェクトを返すので、ES2017の新しいasync/await構文を使って同じことができます:
methods: {
updateMessage: async function () {
this.message = '更新'
console.log(this.$el.textContent) // => ' '
await this.$nextTick()
console.log(this.$el.textContent) // => '更新'
}
}
ソースコード解析
- flushCallbacks について
// 同期実行コールバック関数キュー
function flushCallbacks () {
// 現在の実行状態を非待ち状態に設定する。
pending = false
// 浅いコピーコールバックキュー配列
const copies = callbacks.slice(0)
// リセット・コールバック・キュー(コールバックに相当する= []
callbacks.length = 0
// コールバック関数をループする
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
- timerFunc について
// プラットフォームによって異なる,
// さまざまなバージョン,
// Promiseで優先度を設定する - それぞれ> MutationObserver -> setImmediate -> setTimeout timerFuncにvueのメソッドを割り当てる
// timerFuncが実行されたときにマイクロタスクとして設定するためである。> ブラウザのEventLoopで使用されるマクロ・タスク
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
- nextTickについて
// nextTickメソッドをエクスポートして、Vue内部とvm / Vue外部で呼び出せるようにする。
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
// コールバック関数をコールバック・キューに入れる
callbacks.push(() => {
if (cb) {
// コールバック関数のエラーによってJSスレッド全体がハングアップするのを防ぐために、tryを使用する。... catch
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
// nextTickが現在アイドル状態でなければ、非アイドル状態に設定し、コールバック関数キューの実行を開始する。
if (!pending) {
pending = true
timerFunc()
}
// コールバック関数が渡されない場合、nextTickはPromiseとして返される。
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
雑多な情報
以下はすべて個人的な意見です。
- VueのnextTickは、基本的にJavaScriptの実行原理EventLoopの応用です。
- nextTickのコアは、Promise、MutationObserver、setImmediate、setTimeoutといったJavaScriptのネイティブメソッドを利用して、対応するマイクロ/マクロタスクの実装をシミュレートします。基本的には、JavaScriptでこれらの非同期コールバックタスクキューを利用して、VueフレームワークのVueフレームワーク独自の非同期コールバックキュー
- nextTickは、非同期キューを呼び出すためのVueの内部メソッドであるだけでなく、開発者が実際のプロジェクトで、実際のアプリケーションのDom更新のタイミングに従うロジックに使用することもできます。
- nextTickは、JavaScriptの基本的な実行原則を特定のケースに適用した典型的な例です。
- 非同期の更新キュー機構を導入した理由:
- 更新が同期的である場合、1 つまたは複数の属性に複数の割り当てがあると、UI/Dom のレンダリングが頻繁にトリガされるため、無駄なレンダリングを減らすことができます。
- 同時に、VirtualDOM の導入により、状態が変更されるたびに状態変更信号がコンポーネントに送信され、コンポーネントが VirtualDOM を使用して更新が必要な特定の DOM ノードを計算し、DOM を更新します。非同期レンダリングが重要になります。
クリエイティブ・コモンズ・ライセンスを見る
この作品は下に提供されています。