The problem
<div id="app">
<p ref="ref" v-if="isShow">{{ message }}</p>
<button @click="getWidth">Gets the p element width</button>
</div>
<script>
export default {
data() {
return {
isShow: false.message: 0}},methods: {
getWidth() {
this.isShow = isShow;
this.message = this.$ref.ref.offsetWidth; }}}</script>
Copy the code
Let’s see what’s wrong with the code above, which displays the P tag when the button is clicked and then gets the width to display on the page. In fact, this.$ref.ref is not available immediately after the assignment, so clicking on the button will result in an error.
why
Vue is executed asynchronously when updating the DOM. As long as it listens for data changes, Vue opens a queue and buffers all data changes that occur in the same event loop. In the above code, it is not possible to fetch the latest DOM node immediately after updating the data, since it has not been updated to the DOM structure.
The solution
Vue provides a nextTick method that is used immediately after the data is modified to retrieve the updated DOM.
Official explanation:
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' // DOM has not been updated yet Vue.nextTick(function () { // DOM is updated }) // Use as a Promise (new since 2.1.0, see hints below) Vue.nextTick() .then(function () { // DOM is updated }) Copy the code
So let’s modify the previous code as follows
getWidth() {
this.isShow = isShow;
this.nextTick(() = > {
this.message = this.$ref.ref.offsetWidth; })}Copy the code
This will get the updated DOM.
NextTick source
export function nextTick (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= > {
_resolve = resolve
})
}
}
Copy the code
The above code is the implementation of nextTick in Vue. Let’s take a look at the code.
He’s going to put that callback method in the callbacks array, and then call the timerFunc method, so let’s look at the timerFunc function
if (typeof Promise! = ='undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () = > {
p.then(flushCallbacks)
// 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)
}
isUsingMicroTask = true
} else if(! isIE &&typeofMutationObserver ! = ='undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// Use MutationObserver where native Promise is not available,
PhantomJS, iOS7, Android 4.4
// (#6466 MutationObserver is unreliable in IE11)
let counter = 1
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
} else if (typeofsetImmediate ! = ='undefined' && isNative(setImmediate)) {
// Fallback to setImmediate.
// Technically it leverages the (macro) task queue,
// but it is still a better choice than setTimeout.
timerFunc = () = > {
setImmediate(flushCallbacks)
}
} else {
// Fallback to setTimeout.
timerFunc = () = > {
setTimeout(flushCallbacks, 0)}}Copy the code
FlushCallbacks: flushCallbacks: flushCallbacks: flushCallbacks: flushCallbacks: flushCallbacks: flushCallbacks: flushCallbacks
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
This function copies a callbacks array and executes the callbacks in turn.