Today I made a requirement, and the scene looks like this:

The page pull an interface, this interface to return some of the data, the data is a floating layer components depend on this page, and then I return data in interface shows the floating layer components, showing at the same time, report to some data to the background (these data is the parent component) taken from the interface, the same time, a miracle happened, although I got the data, However, this data has not been updated to the component by the time the float layer is displayed.

The parent component:

<template>
    .....
    <pop ref="pop" :name="name"/>
</template>
<script>
export default {
    .....
    created() {... Data.get({url: XXXX, success: (data) => {// We call the show method directly after the assignment to show that the data reported by the show method is not updated to the child component this.name = data.name this.$refs.pop.show()
            }
        })
    }
}

</script>
Copy the code

Child components

<template>
   <div v-show="isShow">... </div> </template> <script>export default {
    .....
    props: ['name'],
    methods: {
       show() {
           this.isShow = true/ / Report Report ('xxx', {name: this.name})
       }
    }
}

</script>
Copy the code

Problem analysis:

Reasons vue official website has analysis (cn.vuejs.org/v2/guide/re…)

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.

This means that when we set this.name=name on the parent component, the VUE does not update the child component directly (dom updates are also not performed immediately). Instead, the vUE puts all the updates into a queue. Put all the wathCers in the queue as a Watcher update, and wait until the end of the event loop to fetch all the WathCers from the queue to perform the update. In our case, when we call show, our this.name=name is not actually executed, but is queued. The vUE does this based on optimization, of course, otherwise it would be inefficient and undesirable to perform N DOM updates if we had n more assignments.

The update operations described below refer to operations that update the value of data. In VUE, they are queued to be executed asynchronously.

Solution:

1.Use nextTick to delay the execution of the show method (in general, to do anything that needs to happen after the data is actually updated)

As we know from the above analysis, all of our vUE instance updates are queued and deferred asynchronously, either microTask or MacroKTask (microTask or MacroKTask depending on the environment, NextTick source code), according to the event loop, first queued is first executed, so if we execute an action in nextTick, it will look like this.

2. Use setTimeout to delay the execution of the show method

So our solution could be:

this.name = data.name
setTimeout(() => {
 this.$refs.pop.show()
})
Copy the code

or

this.name = data.name
this.$nextTick(() => {
 this.$refs.pop.show()
})
Copy the code

Implementation principle of nextTick

In fact, the implementation principle of nextTick is quite simple. To put it more simply, it is asynchronous. It can be implemented in different execution environments in different ways to ensure that the callback function in nextTick can be executed asynchronously. Why do you do that? Vue updates dom asynchronously.

Post the source code below:

/**
 * Defer a task to execute it asynchronously.
 */
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]()
    }
  }

  // the nextTick behavior leverages the microtask queue, which can be accessed
  // via either native Promise.then or MutationObserver.
  // MutationObserver has wider support, however it is seriously bugged in
  // UIWebView inIOS >= 9.3.3 when triggeredin touch event handlers. It
  // completely stops working after triggering a few times... so, if native
  // Promise is available, we will use it:
  /* istanbul ignore if* /if(typeof Promise ! = ='undefined' && isNative(Promise)) {
    var p = Promise.resolve()
    var logError = err => { console.error(err) }
    timerFunc = () => {
      p.then(nextTickHandler).catch(logError)
      // in problematic UIWebViews, Promise.then doesn't completely break, but // it can get stuck in a weird state where callbacks are pushed into the // microtask queue but the queue isn't being flushed, until the browser
      // needs to do some other work, e.g. handle a timer. Therefore we can
      // "force" the microtask queue to be flushed by adding an empty timer.
      if (isIOS) setTimeout(noop)
    }
  } else if(! isIE && typeof MutationObserver ! = ='undefined' && (
    isNative(MutationObserver) ||
    // PhantomJS and iOS 7.x
    MutationObserver.toString() === '[object MutationObserverConstructor]'
  )) {
    // use MutationObserver where native Promise is not available,
    // e.g. 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 = () => {
      counter = (counter + 1) % 2
      textNode.data = String(counter)
    }
  } else {
    // fallback to setTimeout
    /* istanbul ignore next */
    timerFunc = () => {
      setTimeout(nextTickHandler, 0)
    }
  }

  return functionqueueNextTick (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()
    }
    if(! cb && typeof Promise ! = ='undefined') {
      return new Promise((resolve, reject) => {
        _resolve = resolve
      })
    }
  }
})()

Copy the code

First of all, we can see that this is using the closure feature to return queueNextTick, so what we’re actually calling nextTick is actually calling queueNextTick, and once we call this method, we’ll put the nextTick callback into the queue callbacks, and when the time is right, All callbacks in the callbacks are pulled out and executed to delay execution. I think there are two reasons for using closures: 1. Shared variables like Callbacks, pending, and timerFunc. 2. Avoid double-checking whether timerFunc uses Promise or MutationObserver or setTimeout to implement asynchracy, which is an application of the immediate execution of the function.

NextTickHandler is a function that executes all callbacks from a queue, similar to a microTask task queue. We call vue. $nextTick to put all callbacks into the queue, and when it’s time to execute, we call nextTickHandler to pull them out and execute them.

2, timerFunc this variable, the execution should be realized through the Promise/Mutationobserver/Settimeout asynchronous, put the nextTickHandler to truly asynchronous task queue, wait until the end of the event loop. The nextTickHandler is removed from the task queue and executed. Once the nextTickHandler executes, all callbacks in the callbacks are removed and executed, thus delaying the execution of callbacks passed by nextTick.

From this simple source code analysis, we can draw two conclusions: 1. NextTick can be microTask or MacroTask, rather than fixed, depending on the execution environment. So, if you try to treat all asynchronous tasks in nextTick as microtasks, you’ll run into trouble. NextTick does not guarantee that you will get the updated DOM, depending on whether you assign data first or call nextTick first. Such as:

 new Vue({
      el: '#app'.data() {
        return {
          id: 2
        }
      },
      created() {},mounted() {
        this.$nextTick(() => {
          console.log(document.getElementById('id').textContent) // It prints 2 because nextTick}) this.id = 3}})Copy the code

conclusion

NextTick (callback) if you want to get the updated DOM or child component (depending on the parent component’s value), you can use vue.nexttick (callback) immediately after the update. In the source code, we know that nextTick is not guaranteed to get the updated DOM/ child components