I still remember that I met problems with nextTick for the first time when I was working on a project in the company, and I finally solved the problem through nextTick. Then I went to learn the usage and implementation of nextTick, and I shared some tips on the way of learning, hoping that everyone could gain something from it!
The usage Vue. NextTick ()
NextTick ([callback,context]);
Parameter Description:
{Function} [callback]
: a callback function that provides a promise call without passing;{Object} [context]
The context in which the callback function is executed. By default, it is automatically bound to the calling instance.
// Modify the data
vm.msg = 'Hello'
// DOM has not been updated yet
Vue.nextTick(function () {
// DOM is updated./ / DOM manipulation
})
// Used as a Promise
Vue.nextTick()
.then(function () {
// DOM is updated
})
Copy the code
Vue.nexttick () is one of Vue’s global core apis.
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.
That is, it waits for the DOM update to complete before triggering the callback.
NextTick Usage scenarios
Scenario 1: If you want to manipulate the DOM in the Created hook function, you need to do it in the vue.nexttick () callback.
<template> <div> <h3 ref="title">Hello World! </h3> </div> </template> <script> export default { created() { console.log('created'); console.log(this.$refs.title) this.$nextTick(() => { console.log('created-nextTick'); console.log(this.$refs.title) }) }, mounted() { console.log('mounted'); console.log(this.$refs.title) this.$nextTick(() => { console.log('mounted-nextTick'); console.log(this.$refs.title) }) } } </script>Copy the code
Output result:
Depending on the order of the output values, you can see that the DOM is not being rendered when the Created () hook function is executed. Manipulating the DOM has no effect at this point, whereas a DOM object can be retrieved by using nextTick in Created. The Mounted () hook function has already mounted and rendered the DOM, so there is no problem manipulating the DOM.
Scenario 2: When the data changes and we want to retrieve the DOM structure of the modified data to perform some operations, we also need to put it into the vue.nexttick () callback function.
<template> <div> <h3 ref="title">{{msg}}</h3> <el-button @click="changeMsg">Click</el-button> </div> </template> <script> export default { data() { return { msg: 'hello' } }, methods: { changeMsg() { this.msg = 'changed'; console.log(this.msg); // Order 1: changed console.log(this.$refs.title.innerhtml) $nextTick(() => {console.log(this.$refs.title.innerhtml)) Changed}) console.log(this.$refs.title.innerhtml) // Order 3: hello}}} </script>Copy the code
Through the execution results, we found that the data value was immediately modified after the modification, but the corresponding DOM was not updated in time. NextTick updated DOM through a certain strategy. Next, let’s take a look at its implementation principle.
Realize the principle of
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. 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 attempts to use native Promise.then, MutationObserver, and setImmediate for asynchronous queues, and 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 is updated in the next event loop “TICK”. In most cases we don’t need to worry about this process, but if you want to do something based on the updated DOM state, 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 have to. 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.
The above is the description of the asynchronous update queue on the official website, which perfectly explains the implementation mechanism of nextTick. Why is DOM updating an asynchronous operation? What’s it good for? A: Improved rendering efficiency!!
For a refresher on synchronous asynchronous programming and event loops, see here: juejin.cn/post/685041…
Source analyses
The process would look something like this: The nextTick execution passes a callback function into the Callbacks array, which is essentially a pool of events. After the addition, the timerfunc function is executed based on the pending variable and the pending value is set to true. Timerfunc is a function that performs an asynchronous operation in order to place the callback into the event pool when nextTick is synchronized multiple times. FlushCallbacks is the asynchronous flushCallbacks function that executes all the callbacks in the event pool one by one.
Let’s start with the three important variables mentioned above:
Callbacks: equivalent to an event pool that stores all callbacks;
Pending: indicates whether the callback function is currently executing.
TimerFunc: used to trigger the execution of the callback function;
The core code is posted below:
nextTick
export function nextTick (cb? : Function, ctx? Callbacks. Push (() => {cb.call(CTX)}) if (! Pending) {pending = true // 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
In fact, nextTick(callback) is similar to Promise().resolve().then(callback) or setTimeout(callback,0); So if you want to get THE DOM information after the DOM update, you need to create an asynchronous task after the asynchronous task creation. Use setTimeout to verify this:
<template>
<h3 class="title">{{msg}}</h3>
</template>
<script>
export default {
data() {
return {
msg: 'before'
}
},
mounted() {
this.msg = 'changed';
let box = document.getElementsByClassName('title')[0];
setTimeout(() => {
console.log(box.innerHTML)
})
}
}
</script>
Copy the code