The premise of nextTick

Because Vue drives view updates asynchronously, when we modify data in an event, the view is not updated immediately, but is updated after all data changes in the same event loop are complete. Similar to Event Loop.

The official introduction

First of all, let’s take a look at the introduction on the official website:

Vue.nextTick([callback, context])

  • Parameters:

    • {Function} [callback]
    • {Object} [context]
  • Usage:

    A deferred callback is performed after the next DOM update loop ends. Use this method immediately after modifying the data to get the updated DOM.

// Modify the data
vm.msg = 'Hello'
// When we call DOM data here, it hasn't been updated yet
Vue.nextTick(function () {
    // DOM is updated
})

// 2.1.0 New Promise usage
Vue.nextTick()
    .then(function () {
    // The DOM has been updated
})
Copy the code

New since 2.1.0: Returns a Promise if no callback is provided and in an environment that supports Promise. Note that Vue doesn’t come with Promise’s polyfill, so if your target browser doesn’t support Promise nave, you’ll have to provide polyfill yourself.

DOM update loop

First, instead of updating the DOM as soon as the data changes, Vue implements a responsive DOM update asynchronously after all the data changes in an event loop.

For asynchrony and event loops, check out an article I wrote earlier about asynchrony

If you don’t want to go into detail, here’s a quick summary of the event cycle:

Synchronous code execution => find asynchronous queue, enter execution stack, execute Callback1[event loop 1] => find asynchronous queue, enter execution stack, execute Callback2[event loop 2] =>…..

That is, each asynchronous Callback forms a separate event loop

So we can exit the nextTick trigger

Once the code in the event loop completes => DOM update => trigger the nextTick callback => enter the next loop

Example shows

Talk is cheap, show me the code. —— Linus Torvalds

It may not be possible to get a clear understanding of the nextTick mechanism with just a few conceptual explanations, but let’s look at the last example.

<template> <div class="app"> <div ref="contentDiv">{{content}}</div> <div> <div> gets content after nextTick execution :{{content2}}</div> <div> Gets content before nextTick execution :{{content3}}</div> </div> </template> <script> export default { name:'App', data: { content: 'Before NextTick', content1: '', content2: '', content3: '' }, methods: {{changeContent () enclosing the content = 'After NextTick' / / here to update the content data. This content1 = this. $refs. ContentDiv. InnerHTML / / retrieve data from the DOM this $nextTick (() = > {/ / retrieve data from the DOM in the callback nextTick enclosing content2 = this. $refs. ContentDiv. InnerHTML}) this.content3 = this.$refs.contentDiv.innerHTML } }, mount () { this.changeContent() } } </script>Copy the code

When we open the page, we can find the result as follows:

After NextTick Obtain content Before NextTick execution :Before NextTick Obtain content After NextTick execution :Before NextTick Obtain content Before NextTick execution :Before NextTick Obtain content After NextTick execution :Before NextTickCopy the code

So we can see that although content1 and Content3 are written after content data changes, they are in the same event loop, So content1 and content3 still get ‘Before NextTick’, whereas content2 gets the content written in the NextTick callback and executed After the DOM is updated, so it gets the updated ‘After NextTick’.

Application scenarios

Here are some of the main application scenarios for nextTick

DOM operations are performed in the Created lifecycle

It is not desirable to perform DOM manipulation directly in the Created () lifecycle because the DOM is not being rendered at all. So the solution is to write the DOM manipulation into the vue.nexttick () callback. Or put the operation in the Mounted () hook function

Operations based on DOM structures are required after data changes

After we update the data, if there are operations to be performed based on the updated DOM structure, we should put those operations in the ** vue.nexttick ()** callback

The detailed reasons for this are clearly explained in the official Vue documentation:

In case you haven’t noticed, Vue performs DOM updates asynchronously. Whenever a data change is observed, Vue opens a queue and buffers all data changes that occur in the same event loop. If the same watcher is triggered more than once, it will only be pushed into the queue once. This removal of duplicate data while buffering is important to avoid unnecessary computation and DOM manipulation. Then, in the next event loop, “TICK,” Vue refreshes the queue and performs the actual (de-duplicated) work. Vue internally tries to use native promise.then and MessageChannel for asynchronous queues, or setTimeout(fn, 0) instead if the execution environment does not support it.

For example, when you set vm.someData = ‘new value’, the component does not immediately re-render. When the queue is refreshed, the component updates with the next “tick” when the event loop queue is empty. In most cases we don’t need to worry about this process, but if you want to do something after a DOM status update, it can be tricky. While vue.js generally encourages developers to think in a “data-driven” way and avoid direct contact with the DOM, sometimes we do. To wait for Vue to finish updating the DOM after the data changes, use vue.nexttick (callback) immediately after the data changes. This callback will be called after the DOM update is complete.

Attached: nextTick source code analysis

Personal translation, if there is any problem, please feel free to ask

export const nextTick = (function () {
  // Store all callback functions
  const callbacks = []
  // A flag indicating whether a callback function is being executed
  let pending = false
  // Trigger the execution of the callback function
  let timerFunc
// handle the callback function
  function nextTickHandler () {
    pending = false
    const copies = callbacks.slice(0)
    callbacks.length = 0
    for (let i = 0; i < copies.length; i++) {
      // Execute the callback function
      copies[i]()
    }
  }
  
  // The nextTick behavior takes advantage of the microtask queue
  // It can be implemented via native Promise or MutationObserver
  // MutationObserver already has extensive browser support, however it is still in UIWebView for ios 9.3.3 and above
  // There is a serious Bug in the system. The problem occurs when we touch the event.
  // It stops completely after we trigger for a while, so the native Promise is valid and available, and we'll use it:
  /* istanbul ignore if */
  if (typeof Promise! = ='undefined' && isNative(Promise)) {
    var p = Promise.resolve()
    var logError = err= > { console.error(err) }
    timerFunc = (a)= > {
      p.then(nextTickHandler).catch(logError)
      // In problematic UIWebViews, the promise. then method does not stop completely, but it may stop in one
      // Strange state stuck when we push the callback function into a microtask queue but the queue is not in flush, ok
      // When the browser needs to do something else, such as executing a timing function. So we can force the microtask force
      // The column is flushed by adding an empty timer function
      if (isIOS) setTimeout(noop)
    }
  } else if(! isIE &&typeofMutationObserver ! = ='undefined' && (
    isNative(MutationObserver) ||
    // PhantomJS and iOS 7.x
    MutationObserver.toString() === '[object MutationObserverConstructor]'
  )) {
    // Use MutationObserver when a Promise is unavailable,
    // For example PhantomJS, iOS7, Android 4.4
    var counter = 1
    var observer = new MutationObserver(nextTickHandler)
    var textNode = document.createTextNode(String(counter))
    observer.observe(textNode, {
      characterData: true
    })
    timerFunc = (a)= > {
      counter = (counter + 1) % 2
      textNode.data = String(counter)
    }
  } else {
    // When neither MutationObserver nor Promise can be used
    // We use setTimeOut to do this
    /* istanbul ignore next */
    timerFunc = (a)= > {
      setTimeout(nextTickHandler, 0)}}return function queueNextTick (cb? : Function, ctx? : Object) {
    let _resolve
    callbacks.push((a)= > {
      if (cb) {
        try {
          cb.call(ctx)
        } catch (e) {
          handleError(e, ctx, 'nextTick')}}else if (_resolve) {
        _resolve(ctx)
      }
    })
    if(! pending) { pending =true
      timerFunc()
    }
    if(! cb &&typeof Promise! = ='undefined') {
      return new Promise((resolve, reject) = > {
        _resolve = resolve
      })
    }
  }
})()
Copy the code

As we can see from the source code, timeFunc is a function to delay execution, it has three implementations

  • Promise
  • MutationObserver
  • setTimeout

Promise and setTimeout are familiar to us. Here we will focus on MutationObserver

MutationObserver is a new API in HTML5 that monitors DOM changes. It can listen for child node deletions, property changes, text content changes, and so on on a DOM object. The call is simple, but a little unusual: you need to bind the callback first:

let mo = new MutationObserver(callback)
Copy the code

You get a MutationObserver instance by passing a callback to the constructor of the MutationObserver, which is triggered when the MutationObserver instance listens for changes.

At this point you are just binding the callback to the MutationObserver instance. It is not set which DOM it listens for, node deletion, or property modification. This is accomplished by calling his Observer method:

varDomTarget = the DOM node you want to listen on mo.observe(domTarget, {characterData: true // Listen for text content changes.
})
Copy the code

The role of MutationObserver in nextTick is shown in the figure below. After listening for DOM updates, the callback function is called.

conclusion

  • In the same event loop, nextTick is called only after all synchronous data updates have been executed
  • DOM rendering does not begin until the data in the synchronous execution environment has been fully updated.
  • If multiple NextTicks exist in the same event loop, they are called in the order in which they were originally executed.
  • Each asynchronous callback is executed in a separate event loop with its own separate nextTick
  • Vue DOM view update implementation, using ES6PromiseAnd it’sMutationObserver, used when the environment does not supportsetTimeout(fn, 0)Alternative. All three of these methods are asynchronous apis. Among themMutationObserverSimilar events, but also different; The event is asynchronously triggered. That is, when the DOM changes, it is not triggered immediately, but after all the current DOM operations are complete.

Refer to the link

Vue official documentation – Asynchronous update queue

Ruheng: Briefly understand nextTick in Vue

Personal Github: Reaper622

Welcome to learn and exchange