Why is nextTick in Vue?

Abstract: This article will analyze the role of nextTick, usage scenarios and the principle behind it, hoping to be helpful to everyone.

This article is shared from the HUAWEI CLOUD community " What is the role of nextTick in Vue? ", by CoderBin.

1. What is nextTick

Let's take a look at the official definition of it:

Executes the deferred callback after the next DOM update loop ends. Use this method immediately after modifying the data to get the updated DOM.

What does that mean?

We can understand that Vue executes asynchronously when updating the DOM. When the data changes, Vue will open an asynchronous update queue, and the view needs to wait for all data changes in the queue to complete, and then update it uniformly

for example:

Html structure

<div id="app"> {{ message }} </div>

Build a vue instance

const vm = new Vue({
  el: '#app',
  data: {
    message: '原始值'
 }
})

Modify message

this.message = '修改后的值1' 
this.message = '修改后的值2' 
this.message = '修改后的值3'

At this time, I want to get the latest DOM node of the page, but I find that the old value is obtained.

console.log(vm.$el.textContent) // 原始值

This is because when the message data changes, vue will not update the Dom immediately, but put the operation of modifying the data in an asynchronous operation queue

If we keep modifying the same data, the asynchronous operation queue will also be deduplicated

After waiting for all data changes in the same event loop to complete, the events in the queue will be processed and the DOM will be updated

Why have nexttick

for example

{{num}} 
for(let i=0; i<100000; i++){ 
  num = i 
}

If there is no nextTick update mechanism, then every time the value of num is updated, the view will be updated (the above code will update the view 100,000 times). With the nextTick mechanism, it only needs to be updated once, so nextTick is essentially an optimization strategy

2. Usage scenarios

If you want to get the updated DOM structure immediately after modifying the data, you can use Vue.nextTick()

  • The first parameter is: callback function (you can get the latest DOM structure)
  • The second parameter is: the execution function context
// 修改数据
vm.message = '修改后的值'
// DOM 还没有更新
console.log(vm.$el.textContent) // 原始的值
Vue.nextTick(function () {
 // DOM 更新了
  console.log(vm.$el.textContent) // 修改后的值
})

Using the vm.$nextTick() instance method in the component only needs to pass this.$nextTick(), and this in the callback function will be automatically bound to the current Vue instance

this.message = '修改后的值'
console.log(this.$el.textContent) // => '原始的值'
this.$nextTick(function () {
    console.log(this.$el.textContent) // => '修改后的值'
})

$nextTick() will return a Promise object, which can be used to accomplish the same thing with async/await

this.message = '修改后的值'
console.log(this.$el.textContent) // => '原始的值'
await this.$nextTick()
console.log(this.$el.textContent) // => '修改后的值'

3. Realization principle

Source code location: /src/core/util/next-tick.js

callbacks is the asynchronous operation queue

After the callback function is added, the timerFunc function is executed. Pending is used to identify that the same time can only be executed once

export function nextTick(cb?: Function, ctx?: Object) {
 let _resolve;
 // cb 回调函数会经统一处理压入 callbacks 数组
 callbacks.push(() => {
 if (cb) {
 // 给 cb 回调函数执行加上了 try-catch 错误处理
 try {
 cb.call(ctx);
 } catch (e) {
 handleError(e, ctx, 'nextTick');
 }
 } else if (_resolve) {
 _resolve(ctx);
 }
 });
 // 执行异步延迟函数 timerFunc
 if (!pending) {
    pending = true;
 timerFunc();
 }
 // 当 nextTick 没有传入函数参数的时候,返回一个 Promise 化的调用
 if (!cb && typeof Promise !== 'undefined') {
 return new Promise(resolve => {
      _resolve = resolve;
 });
 }
}

The timerFunc function definition, here is to determine which method to call according to what method the current environment supports, respectively:

Promise.then、MutationObserver、setImmediate、setTimeout

Use any of the above methods to downgrade

export let isUsingMicroTask = false
if (typeof Promise !== 'undefined' && isNative(Promise)) {
 //判断1:是否原生支持Promise
 const p = Promise.resolve()
 timerFunc = () => {
 p.then(flushCallbacks)
 if (isIOS) setTimeout(noop)
 }
 isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
 isNative(MutationObserver) ||
 MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
 //判断2:是否原生支持MutationObserver
 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 (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
 //判断3:是否原生支持setImmediate
 timerFunc = () => {
 setImmediate(flushCallbacks)
 }
} else {
 //判断4:上面都不行,直接用setTimeout
 timerFunc = () => {
 setTimeout(flushCallbacks, 0)
 }
}

Whether it is a micro task or a macro task, it will be used in flushCallbacks

Here, make a copy of the functions in callbacks, and leave callbacks empty.

Execute the functions in the callbacks in turn

function flushCallbacks () {
  pending = false
 const copies = callbacks.slice(0)
 callbacks.length = 0
 for (let i = 0; i < copies.length; i++) {
    copies[i]()
 }
}

Fourth, the final summary

  1. Put the callback function into callbacks to wait for execution
  2. Put the execution function in the microtask or macrotask
  3. The event loops to the microtask or macro task, and the execution function executes the callbacks in the callbacks in turn.

 

Click Follow to learn about HUAWEI CLOUD's new technologies for the first time~

{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4526289/blog/5584854