Cette fois, je vais vous montrer comment utiliser Vue nextTick et quelles sont les précautions lors de l'utilisation de Vue nextTick. Voici un cas pratique, jetons un oeil.
export default { data () { return { msg: 0 } }, mounted () { this.msg = 1 this.msg = 2 this.msg = 3 }, watch: { msg () { console.log(this.msg) } } }
Après avoir exécuté ce script, on devine qu'après 1000m, il s'imprimera dans l'ordre : 1, 2, 3. Mais en réalité, il ne sera émis qu’une seule fois : 3. Pourquoi cela arrive-t-il ? Découvrons-le.
queueWatcher
Nous définissons watch pour écouter msg, qui sera en fait appelé par Vue comme vm.$watch(keyOrFn, handler, options). $watch est une fonction liée à vm lorsque nous l'initialisons, utilisée pour créer des objets Watcher. Voyons ensuite comment le gestionnaire est géré dans Watcher :
this.deep = this.user = this.lazy = this.sync = false ... update () { if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } } ...
Paramètre initial this.deep = this.user = this.lazy = this.sync = false, c'est-à-dire lorsqu'une mise à jour est déclenché, exécutera la méthode queueWatcher :
const queue: Array<Watcher> = [] let has: { [key: number]: ?true } = {} let waiting = false let flushing = false ... export function queueWatcher (watcher: Watcher) { const id = watcher.id if (has[id] == null) { has[id] = true if (!flushing) { queue.push(watcher) } else { // if already flushing, splice the watcher based on its id // if already past its id, it will be run next immediately. let i = queue.length - 1 while (i > index && queue[i].id > watcher.id) { i-- } queue.splice(i + 1, 0, watcher) } // queue the flush if (!waiting) { waiting = true nextTick(flushSchedulerQueue) } } }
La fonction flushSchedulerQueue dans nextTick(flushSchedulerQueue) ici est en fait la mise à jour de la vue de l'observateur :
function flushSchedulerQueue () { flushing = true let watcher, id ... for (index = 0; index < queue.length; index++) { watcher = queue[index] id = watcher.id has[id] = null watcher.run() ... } }
En plus Concernant la variable d'attente, il s'agit d'un indicateur très important, qui garantit que le rappel flushSchedulerQueue ne peut être placé qu'une seule fois dans les rappels. Jetons ensuite un coup d'œil à la fonction nextTick. Avant de parler de nexTick, vous devez avoir une certaine compréhension de Event Loop, microTask et macroTask. Vue nextTick utilise également principalement ces principes de base. Si vous ne le comprenez pas encore, vous pouvez vous référer à mon article Introduction à Event Loop. Voyons maintenant son implémentation :
export const nextTick = (function () { const callbacks = [] let pending = false let timerFunc function nextTickHandler () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } } // An asynchronous deferring mechanism. // In pre 2.4, we used to use microtasks (Promise/MutationObserver) // but microtasks actually has too high a priority and fires in between // supposedly sequential events (e.g. #4521, #6690) or even between // bubbling of the same event (#6566). Technically setImmediate should be // the ideal choice, but it's not available everywhere; and the only polyfill // that consistently queues the callback after all DOM events triggered in the // same loop is by using MessageChannel. /* istanbul ignore if */ if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { timerFunc = () => { setImmediate(nextTickHandler) } } else if (typeof MessageChannel !== 'undefined' && ( isNative(MessageChannel) || // PhantomJS MessageChannel.toString() === '[object MessageChannelConstructor]' )) { const channel = new MessageChannel() const port = channel.port2 channel.port1.onmessage = nextTickHandler timerFunc = () => { port.postMessage(1) } } else /* istanbul ignore next */ if (typeof Promise !== 'undefined' && isNative(Promise)) { // use microtask in non-DOM environments, e.g. Weex const p = Promise.resolve() timerFunc = () => { p.then(nextTickHandler) } } else { // fallback to setTimeout timerFunc = () => { setTimeout(nextTickHandler, 0) } } return function queueNextTick (cb?: Function, ctx?: Object) { let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true timerFunc() } // $flow-disable-line if (!cb && typeof Promise !== 'undefined') { return new Promise((resolve, reject) => { _resolve = resolve }) } } })()
Tout d'abord, Vue le simule via un rappel array<.> File d'attente des événements, les événements de la file d'attente des événements sont appelés via la méthode nextTickHandler et ce qui est exécuté est déterminé par timerFunc. Jetons un coup d'oeil à la définition de timeFunc :
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { timerFunc = () => { setImmediate(nextTickHandler) } } else if (typeof MessageChannel !== 'undefined' && ( isNative(MessageChannel) || // PhantomJS MessageChannel.toString() === '[object MessageChannelConstructor]' )) { const channel = new MessageChannel() const port = channel.port2 channel.port1.onmessage = nextTickHandler timerFunc = () => { port.postMessage(1) } } else /* istanbul ignore next */ if (typeof Promise !== 'undefined' && isNative(Promise)) { // use microtask in non-DOM environments, e.g. Weex const p = Promise.resolve() timerFunc = () => { p.then(nextTickHandler) } } else { // fallback to setTimeout timerFunc = () => { setTimeout(nextTickHandler, 0) } }
setImmediate, MessageChannel VS setTimeout
Nous définissons d'abord setImmediate et MessageChannel Pourquoi devrions-nous les utiliser en premier pour créer une macroTask au lieu de setTimeout ? HTML5 stipule que le délai minimum de setTimeout est de 4 ms, ce qui signifie que dans des circonstances idéales, le rappel asynchrone le plus rapide pouvant se déclencher est de 4 ms. Vue utilise de nombreuses fonctions pour simuler des tâches asynchrones, avec un seul objectif : rendre le rappel asynchrone et appelé le plus tôt possible. Les délais de MessageChannel et setImmediate sont évidemment inférieurs à ceux de setTimeout.Résoudre les problèmes
Avec ces fondements à l'esprit, examinons à nouveau les problèmes mentionnés ci-dessus. Étant donné que le mécanisme d'événements de Vue planifie l'exécution via la file d'attente des événements, il attendra que le processus principal soit inactif avant de planifier, alors revenez en arrière et attendez que tous les processus soient terminés avant de mettre à jour. Cet avantage en termes de performances est évident, par exemple : Il existe désormais une situation où la valeur de test sera exécutée 1000 fois par ++Question intéressante
var vm = new Vue({ el: '#example', data: { msg: 'begin', }, mounted () { this.msg = 'end' console.log('1') setTimeout(() => { // macroTask console.log('3') }, 0) Promise.resolve().then(function () { //microTask console.log('promise!') }) this.$nextTick(function () { console.log('2') }) } })
Lorsque MessageChannel et setImmediate sont pris en charge, leur ordre d'exécution est prioritaire sur setTimeout (dans IE11/Edge, le délai setImmediate peut être inférieur à 1 ms, tandis que setTimeout a un délai minimum de 4 ms, donc setImmediate exécute le fonction de rappel plus tôt que setTimeout(0) Deuxièmement, parce que le tableau de rappel est reçu en premier dans la file d'attente des événements), 2 sera imprimé, puis 3
. être imprimé. Dans le cas où MessageChannel et setImmediate ne sont pas pris en charge, timeFunc sera défini via Promise, et l'ancienne version de Vue avant 2.4 exécutera la promesse en premier. Cette situation fera que l'ordre deviendra : 1, 2, promesse, 3. Étant donné que this.msg doit d'abord déclencher la fonction de mise à jour dom, la fonction de mise à jour dom sera d'abord collectée par le rappel dans la file d'attente temporelle asynchrone, puis Promise.resolve().then(function () { console.log('promise! ')} sera défini. ) une telle microTâche, puis la définition de $nextTick sera collectée par le rappel. Nous savons que la file d'attente satisfait au principe du premier entré, premier sorti, donc les objets collectés par le rappel sont exécutés en premier.
Je pense que vous maîtrisez la méthode après avoir lu le cas dans cet article. Pour des informations plus intéressantes, veuillez prêter attention aux autres articles connexes sur le site Web chinois de php !
Lecture recommandée :
Comment utiliser JS pour réaliser une animation de dégradé de transparence
Comment utiliser JS pour réaliser un pliage simple et animation de déroulement
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!