Why does Vue use asynchronous update queues?
This article assumes that you already have some knowledge of Vue’s change detection and rendering mechanisms. If you do not know, please refer to “Simple Vue change detection Principles”, “PPT: Simple Vue. Js-virtualdom”.
Asynchronous update queues refer to Vue asynchronously performing DOM updates when state changes.
In project development, we will encounter such a scenario: when we want to obtain the updated DOM after changing the state, we often get the old DOM before the update. We need to use the vm.$nextTick method to obtain the DOM asynchronously, for example:
Vue.component('example', { template: '<span>{{ message }}</span>', data: function () { return { message: }}, methods: {updateMessage: Function () {this.message = 'update done' console.log(this.$el.textContent) // => 'not update' this.$nextTick(function () {this.message = 'update done' console.log(this.$el.textContent)) The console. The log (enclosing $el. TextContent) / / = > 'update complete'}}}})
We all know it’s a hassle, but why does Vue do it?
First let’s assume that Vue performs DOM updates synchronously. What’s the problem?
If we update the DOM synchronously, we will have a problem. If we update the DOM synchronously N times, the DOM will be updated N times. The pseudo-code is as follows:
This. message = 'update completed' // DOM updated once this.message = 'update completed 2' // DOM updated twice this.message =' update completed 3' // DOM updated 3 times this.message = 'update completed 2' // DOM updated 3 times this.message =' update completed 2' // DOM updated 3 times this.message = 'Update completed 4' // DOM updated four times
But in fact, what we really want is the last update, which means that the first three DOM updates can be omitted, and we just need to wait for all the states to be modified before rendering to reduce some of the unnecessary work.
Vue2.0 began to introduce Virtualdom. Every time the state changes, the signal of the state change will be sent to the component. The component uses Virtualdom to calculate the specific DOM node that needs to be updated, and then updates the DOM. The rendering process after each state update requires more computation and wasted performance, so asynchronous rendering becomes more critical.
VIrtualDOM is used inside the component to render, meaning that the component doesn’t really care which state has changed, it only needs to compute once to know which nodes need to be updated. That is, if you change N states, you only need to send a signal to bring the DOM up to date. Such as:
This. message = 'update done' this.age = 23 this.name = berwin
In the code we changed three states in three times, but Vue only renders once. Because VIrtualDOM only needs to update the DOM of an entire component once, it doesn’t care which specific state the update signal is coming from.
So how do you delay rendering until all the states are modified? Simply defer rendering until the end of this event cycle or the next event cycle. That is, at the end of the loop, after all the previous state updates have been executed, it can ignore the syntax of the previous state updates and render only once at the end of the loop, no matter how many state updates have been written.
Postponing rendering to the end of this cycle is much faster than postponing rendering to the next cycle, so Vue prioritises postponing rendering to the end of this cycle and demotes to the next cycle if the execution environment does not support it.
Of course, Vue’s change detection mechanism means that it must signal the render every time the state changes, but Vue checks if the task already exists in the queue after receiving the signal to ensure that there are no duplicates in the queue. Add the render operation to the queue if it does not exist.
All render operations in the queue are then delayed asynchronously and the queue is emptied. The same render operation is not repeatedly added to the queue when the state is repeatedly modified in the same round of events.
So when we use Vue, we update the DOM asynchronously after changing the state.
Having said that, let’s talk a little bit about what an event loop is.
Event loop mechanism
There is something called the execution stack in JS. When a function call is executed, a new execution environment will be created and pushed onto the stack to start executing the code in the function. When the code in the function is finished executing, the execution environment will be ejected from the stack. When the stack is empty, it means that the execution is finished.
One of the problems here is that you don’t just have synchronous code, you also have asynchronous code. After an asynchronous task is completed, the task is added to the task queue. Such as:
setTimeout(_=>{}, 1000)
SetTimeout in the code adds the callback function to the task queue after one second. There are actually two types of asynchronous queues: micro tasks and macro tasks.
The difference between microtask and macro task is that when the execution stack is empty, the system checks whether there are any tasks in the microtask queue and takes the tasks out of the microtask queue one by one. When the microtask queue is empty, take out a task from the macro task queue to execute, check the microtask queue after execution, and take out a task from the macro task queue to execute after the microtask queue is empty. This continuous succession of tasks is called an event loop.
There are the following types of events that belong to microtasks:
- Promise.then
- MutationObserver
- Object.observe
- process.nextTick
There are several types of events that belong to macrotasks:
- setTimeout
- setInterval
- setImmediate
- MessageChannel
- requestAnimationFrame
- I/O
- UI interaction events
eggs
From the previous section, we learned that Vue updates by default add functions that perform render operations to the microtask queue, which takes precedence over macro tasks. Therefore, it is interesting that if we register the function with setTimeout in the code and then modify the state, we can still get the updated DOM in the setTimeout callback, for example:
new Vue({ // ... methods: { // ... example: Function () {// use setTimeout to register the callback setTimeout(_ => {DOM is now updated}, This. Message = 'changed'}}})
The reason for this is that modifying data by default adds the callback for updating the DOM to the microtask queue. If we put the DOM retrieval into the MacroTask, the location of the registration becomes less important. Wherever you register, you update the DOM first and then get the DOM. Because a task in a microtask executes before a task in a macrotask.
If you use vm.$nextTick to insert tasks into the microtask queue, the order in which the tasks are registered is important, because both the render operation and the callbacks registered with vm.$nextTick add tasks to the microtask queue, and the callbacks are executed in the same order as the inserted queue. That is, The queue inserted first is executed first. Such as:
new Vue({ // ... methods: { // ... example: Function () {this.message = 'changed'}}}) {this.message = 'changed'}}})
The code registers the task with vm.$nextTick first, and then modiates the data, so it takes precedence over the render operation in the microtask queue, so the DOM on the page does not change when the callback is executed.
To get the updated DOM, you must first modify the data and then register the callback with vm.$nextTick, for example:
new Vue({ // ... methods: { // ... example: Function () {this.message = 'changed';}}} function () {this.message = 'changed';
As you can see in the code, rendering is performed earlier in the microtask queue than vm.$nextTick callback, so rendering is performed first and then vm.$nextTick callback is executed, so the updated DOM can be retrieved in the callback.