What is nextTick

$ NextTick: According to the official documentation, it executes a callback function after DOM updates and returns a Promise (if supported)

// Modify data vm.msg = "Hello"; // DOM has not been updated vue. nextTick(function() {// DOM is updated});

If you understand EventLoop, you start updating the DOM at the beginning of the next EventLoop, avoiding frequent operations that would cause the page to redraw and reflow.

Here’s a quote from the official document:

In case you haven’t noticed, Vue is when updating the DOM
Asynchronous execution. As soon as a data change is heard, Vue starts a queue and buffers all data changes that occur in the same event loop. If the same watcher is triggered multiple times, it will only be pushed into the queue once.


This removal of duplicate data while buffering is very important to avoid unnecessary computation and DOM manipulation. Then, in the next event loop, “tick,” Vue flushes the queue and does the actual (de-duplicated) work. VUE internally tries to use native for asynchronous queues
Promise.then,
MutationObserver
setImmediate, if the execution environment does not support it
setTimeout(fn, 0)Instead.

Columns such as when vm.text = ‘new value’ is set, the component is not immediately re-rendered, when the queue is refreshed, the component is updated in the next event loop ‘tick’,

<div id="example">{{message}}</div> var vm = new Vue({ el: '#example', data: { message: $el.textContent == 'new message' // false vue. nextTick(function () {$el.textContent == 'new message'); vm.$el.textContent === 'new message' // true })

$nextTick is typically used when the latest DOM data is available immediately after the this.xx=’xx’ data is set. Since DOM updates occur asynchronously, this method is needed for fetching the DOM.

  • VUE asynchronous update policy

Update process (source parsing)

  1. When the data is modified, the watcher will listen for the change and enqueue the change:
/*
 * Subscriber interface.
 * Will be called when a dependency changes.
 */
Watcher.prototype.update = function update() {
  /* istanbul ignore else */
  if (this.lazy) {
    this.dirty = true;
  } else if (this.sync) {
    this.run();
  } else {
    queueWatcher(this);
  }
};
  1. And add a FlushScheduleQueue callback using the nextTick method
/** * Push a watcher into the watcher queue. * Jobs with duplicate IDs will be skipped unless it's * pushed when the queue is being flushed. */ function queueWatcher(watcher) { var 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. var 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; if (! config.async) { flushSchedulerQueue(); return; } nextTick(flushSchedulerQueue); }}}
  1. FlushScheduleQueue is added to the callback array and executed asynchronously
function nextTick(cb, ctx) { var _resolve; callbacks.push(function() { if (cb) { try { cb.call(ctx); / /!!!!! } catch (e) {handleError(e, CTX, "nextTick"); } } else if (_resolve) { _resolve(ctx); }}); if (! Pending) {// asynchronously execute the action see timerFunc pending = true; timerFunc(); } // $flow-disable-line if (! cb && typeof Promise ! == "undefined") { return new Promise(function(resolve) { _resolve = resolve; }); }}
  1. The TimerFunc operation is performed asynchronously using the sequential judgment using: Promise.then=>MutationObserver=> SeTimMediate => SetTimeout
var timerFunc; if (typeof Promise ! == "undefined" && isNative(Promise)) { var p = Promise.resolve(); timerFunc = function() { p.then(flushCallbacks); // 1. Promise.then if (isIOS) { setTimeout(noop); }}; isUsingMicroTask = true; } else if ( ! isIE && typeof MutationObserver ! == "undefined" && (isNative(MutationObserver) || MutationObserver.toString() === "[object MutationObserverConstructor]")  ) { // 2. MutationObserver var counter = 1; var observer = new MutationObserver(flushCallbacks); var textNode = document.createTextNode(String(counter)); observer.observe(textNode, { characterData: true, }); timerFunc = function() { counter = (counter + 1) % 2; textNode.data = String(counter); }; isUsingMicroTask = true; } else if (typeof setImmediate ! == "undefined" && isNative(setImmediate)) { // 3. setImmediate timerFunc = function() { setImmediate(flushCallbacks); }; } else { //4. setTimeout timerFunc = function() { setTimeout(flushCallbacks, 0); }; }
  1. FlushCallbacks traverse all callbacks and execute
function flushCallbacks() { pending = false; var copies = callbacks.slice(0); callbacks.length = 0; for (var i = 0; i < copies.length; i++) { copies[i](); }}
  1. This includes the previously added FlushScheduleQueue, which updates the component using the run method of the watcher in the Queue
for (index = 0; index < queue.length; index++) {
  watcher = queue[index];
  watcher.run();
}

conclusion

So that’s how Vue’s NextTick method works. To summarize:

  1. Vue uses an asynchronous queue to control the execution of DOM updates and nextTick callbacks
  2. MicroTask, because of its high-priority nature, ensures that the microtasks in the queue are executed before an event loop
  3. Due to compatibility issues, Vue had to do a demotion scheme from microTask to macroTask

reference

  • Vue-nextTick source address
  • Comprehensive analysis of Vue. NextTick implementation principle