Preface: Last year, when I was reading Zhihu, I saw a question about vue’s nextTick principle in an article. It occurred to me that ALTHOUGH I had used this method a lot, I had never known why, so I scratched the source code and started writing this article for the record. However, due to work and life have been busy, until today to revise the sorting out. If you have any mistakes or omissions, please correct them in the comments section
I. Functions of nextTick
This.$nextTick(cb) delays the callback until after the next DOM update.
First of all, we know that one of the characteristics of VUE is responsiveness. It updates the DOM asynchronously. If a Watcher is triggered multiple times, only the last time is valid. This eliminates duplicate data and unnecessary DOM manipulation, which is a good optimization, but can sometimes cause problems if we need to fetch modified data or an updated DOM. For example, a few days ago, a student in the group asked, “Why can’t WE get dom with refs?” . The screenshot has only one line of code and very little information, but if the code does not make mistakes, it is most likely caused by the above reasons. Then it is recommended to set a nextTick to solve the problem.
Second, the principle of nextTick
NextTick source code is relatively simple, here according to the source code structure step by step analysis.
NextTick. Js file has six main members, including three data: isUsingMicroTask, callbacks, pending, and three methods: flushCallbacks, timerFunc, and nextTick.
Boolean isUsingMicroTask as export data is only modified here not used; Callbacks are, as the name suggests, a collection of callbacks; The Boolean value pending indicates the pending state.
FlushCallbacks the method that executes all callbacks for the refresh.
function flushCallbacks () {
pending = false // Drop the wait state
const copies = callbacks.slice(0) // Copy the callback array
callbacks.length = 0 // Reset the callback array
for (let i = 0; i < copies.length; i++) { // Loop the callback
copies[i]()
}
}
Copy the code
TimerFunc is the asynchronous delay wrapping method. Defines the task that triggers the flushCallbacks method (macro or microtask depending on the environment in which it is run). If the current environment supports microtasks Promise or MutationObserver, use microtasks first. If not, use the macro task setImmediate or setTimeout instead. This is related to the operation mechanism of event Loop. In my understanding, after each tick of event loop is finished (the microtask queue is finished), the browser executes render and then starts the next macro task. If you use macro tasks, it really means “after dom update.”
The core nextTIck method
export function nextTick (cb? :Function, ctx? :Object) {
let _resolve // Define a resolve pointer
callbacks.push(() = > {
if (cb) { // If there is a callback, the CTX context is used to execute the callback
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')}}else if (_resolve) { // If there is no callback and the current environment can use Promise,
_resolve(ctx) // Execute the resolve pointer}})if(! pending) {// If you are not in a waiting state,
pending = true // Suspend the state and execute timerFunc
timerFunc()
}
// $flow-disable-line
if(! cb &&typeof Promise! = ='undefined') { // If there is no callback and the current environment can use Promise,
return new Promise(resolve= > { // Return an implementation promise and save resolve
_resolve = resolve
})
}
}
Copy the code
The first time I looked at it I was wondering, why should I return a promise when there is no CB at the end when I already tweet micro-tasks in timerFunc?
Until I saw another use of nextTick: await this.$nextTick().