This is the 23rd day of my participation in Gwen Challenge

Vue.nexttick ([callback, context]) : {Function} [callback]
	{Object} [context] usage: Executes a deferred callback after the next DOM update loop. Use this method immediately after modifying the data to get the updated DOM.Copy the code

1. Give examples

<template>
  <div>
    <div ref="msg">{{msg}}</div>
    <div v-if="msg1">out $nextTick: {{msg1}}</div>
    <div v-if="msg2">in $nextTick: {{msg2}}</div>
    <div v-if="msg3">out $nextTick: {{msg3}}</div>
    <button @click="changeMsg"> changeMsg </button>
  </div>
</template>

<script>
export default {
  data(){
    return{
      msg: 'Hello Vue'.msg1: ' '.msg2: ' '.msg3: ' '}},methods: {
    changeMsg() {
      this.msg = "Hello world"
      this.msg1 = this.$refs.msg.innerHTML
      this.$nextTick(() = > {
        this.msg2 = this.$refs.msg.innerHTML
      })
      this.msg3 = this.$refs.msg.innerHTML
    }
  }
}
</script>
Copy the code

After the click:

As you can see from the figure above, msG1 and MSG3 still display the content before the transformation, while MSG2 displays the content after the transformation. The root cause is that DOM updates in Vue are asynchronous.

2. Data change dom update and principle analysis of nextTick

2.1 Data Changes

Vue bidirectional data binding relies on ES5’s Object.defineProperty, which creates getters and setters for each property during data initialization to turn data into responsive data. Modifying a property value, such as this. MSG = Hello world, actually fires the setter.

Data changes trigger the set function

Object.defineProperty(obj, key, {
  enumerable: true.configurable: true.// The set function is triggered to update the DOM through a series of operations
  set: function reactiveSetter (newVal) {
    const value = getter ? getter.call(obj) : val
    if(getter && ! setter)return
    if (setter) {
      setter.call(obj, newVal)
    } else{ val = newVal } childOb = ! shallow && observe(newVal) dep.notify()// Run the dep notify method}})Copy the code

The dep.notify method is executed

export default class Dep {
  constructor() {
    this.id = uid++
    this.subs = []
  }
  notify() {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      // The update method is actually iterated over the elements in the subs array
      subs[i].update()
    }
  }
}
Copy the code
When the data is referenced, such as <div>{{MSG}}</div>, the get method is executed and the render Watcher is added to the subs array. When the data is changed, Watcher's update method is executed to update the data.Copy the code
update () {
  /* istanbul ignore else */
  if (this.lazy) {
    this.dirty = true
  } else if (this.sync) {
    this.run()
  } else {
    queueWatcher(this) / / queueWatcher execution}}Copy the code

The update method finally executes queueWatcher

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) {// Ensure that nextTick executes only once through waiting
      waiting = true
      The queueWatcher method finally passes the flushSchedulerQueue into nextTick for execution
      nextTick(flushSchedulerQueue)
    }
  }
}
Copy the code

Execute the flushSchedulerQueue method

function flushSchedulerQueue () {
  currentFlushTimestamp = getNow()
  flushing = true
  let watcher, id
  ...
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    // Iterate through the render Watcher's run method to complete the view update
    watcher.run()
  }
  // Reset the waiting variable
  resetSchedulerState()
  ...
}
Copy the code

That is, when a data change finally passes the flushSchedulerQueue into nextTick, executing the flushSchedulerQueue will iterate through the watcher.run() method, The watcher.run() method eventually does the view update, so let’s see what the key nextTick method is.

2.2 nextTick

The nextTick method is pushed into the Callbacks array by the incoming callback, and the timerFunc method is executed

export function nextTick (cb? :Function, ctx? :Object) {
  let _resolve
  // Push into the callbacks array
  callbacks.push(() = > {
     cb.call(ctx)
  })
  if(! pending) { pending =true
    // Execute the timerFunc method
    timerFunc()
  }
}
Copy the code

timerFunc

let timerFunc
// Determine whether to support Promise natively
if (typeof Promise! = ='undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () = > {
    // Use the Promise to execute the flushCallbacks if native Promise is supported
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
// Determine whether MutationObserver is supported natively
} else if(! isIE &&typeofMutationObserver ! = ='undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  let counter = 1
  // Use MutationObserver to flushCallbacks if native MutationObserver is supported
  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
// Determine whether setImmediate is native supported
} else if (typeofsetImmediate ! = ='undefined' && isNative(setImmediate)) {
  timerFunc = () = > {
  SetImmediate Executes flushCallbacks with setImmediate if native supports setImmediate
    setImmediate(flushCallbacks)
  }
// Use setTimeout 0 when neither is supported
} else {
  timerFunc = () = > {
    // use setTimeout to flushCallbacks
    setTimeout(flushCallbacks, 0)}}FlushCallbacks finally executes the callback passed in by the nextTick method
function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}
Copy the code

NextTick uses microTask in preference to macroTask.

That is, tasks in nextTick actually execute asynchronously, and nextTick(callback) is similar to promise.resolve ().then(callback), or setTimeout(callback, 0).

In other words, vue’s view update nextTick(flushSchedulerQueue) is the same as setTimeout(flushSchedulerQueue, 0), and will execute flushSchedulerQueue asynchronously. So we don’t update the DOM immediately when this. MSG = Hello world.

To read DOM information after the DOM update, we need to create an asynchronous task after the creation of the asynchronous task.

To test this idea, let’s use setTimeout instead of nextTick. The following code verifies our idea.

<template>
  <div class="box">{{msg}}</div>
</template>

<script>
export default {
  name: 'index',
  data () {
    return {
      msg: 'hello'
    }
  },
  mounted () {
    this.msg = 'world'
    let box = document.getElementsByClassName('box') [0]
    setTimeout(() = > {
      console.log(box.innerHTML) // world}}})Copy the code

If we nextTick before the data is modified, the asynchronous task we add will be executed before the rendered asynchronous task and the updated DOM will not be retrieved.

<template>
  <div class="box">{{msg}}</div>
</template>

<script>
export default {
  name: 'index',
  data () {
    return {
      msg: 'hello'
    }
  },
  mounted () {
    this.$nextTick(() = > {
      console.log(box.innerHTML) // hello
    })
    this.msg = 'world'
    let box = document.getElementsByClassName('box') [0]}}Copy the code

3. Application scenarios

  1. DOM operations performed by the Created () hook function in the Vue lifecycle must be placed in the vue.nexttick () callback

The DOM doesn’t actually render at the time the Created () hook function executes, and DOM manipulation is useless, so make sure you put the DOM manipulation js code in the view.nexttick () callback. The mounted() hook function is the mounted() hook function, because it executes when all DOM operations are completed.

  1. An operation to be performed after data changes that requires the use of a DOM structure that changes with the data should be placed in the vue.nexttick () callback.