This article mainly introduces a simple understanding of the nextTick method in Vue. Now I will share it with you and give you a reference.
nextTick in Vue involves the asynchronous update of DOM in Vue. It feels very interesting and I learned about it specially. The source code of nextTick involves a lot of knowledge, many of which I don’t quite understand. Let’s introduce nextTick based on some of my own insights.
1. Example
Let’s take an example to learn about DOM updates in Vue and the role of nextTick.
Template
<p class="app"> <p ref="msgp">{{msg}}</p> <p v-if="msg1">Message got outside $nextTick: {{msg1}}</p> <p v-if="msg2">Message got inside $nextTick: {{msg2}}</p> <p v-if="msg3">Message got outside $nextTick: {{msg3}}</p> <button @click="changeMsg"> Change the Message </button> </p>
Vue instance
new Vue({ el: '.app', data: { msg: 'Hello Vue.', msg1: '', msg2: '', msg3: '' }, methods: { changeMsg() { this.msg = "Hello world." this.msg1 = this.$refs.msgp.innerHTML this.$nextTick(() => { this.msg2 = this.$refs.msgp.innerHTML }) this.msg3 = this.$refs.msgp.innerHTML } } })
Before clicking
After clicking
It can be known from the figure: the content displayed by msg1 and msg3 is before the transformation, while the content displayed by msg2 is after the transformation. The fundamental reason is that DOM updates in Vue are asynchronous (detailed explanation below).
2. Application Scenarios
Let’s learn about the main application scenarios and reasons of nextTick.
DOM operations performed in the created() hook function of the Vue life cycle must be placed in the callback function of Vue.nextTick()
In fact, when the created() hook function is executed, the DOM No rendering has been performed, and DOM operations at this time are in vain, so the js code for DOM operations must be put into the callback function of Vue.nextTick(). Corresponding to this is the mounted() hook function, because all DOM mounting and rendering have been completed when this hook function is executed, there will be no problem in performing any DOM operations in this hook function.
When there is an operation to be performed after the data changes, and this operation requires the use of a DOM structure that changes as the data changes, this operation should be put into the callback function of Vue.nextTick().
The specific reasons are explained in detail in Vue’s official documentation:
Vue performs DOM updates asynchronously. As soon as data changes are observed, Vue will open a queue and buffer all data changes that occur in the same event loop. If the same watcher is triggered multiple times, it will only be pushed into the queue once. This deduplication during buffering is important to avoid unnecessary calculations and DOM operations. Then, on the next event loop "tick", Vue flushes the queue and performs the actual (deduplicated) work. Vue internally tries to use native Promise.then and MessageChannel for asynchronous queues. If the execution environment does not support it, setTimeout(fn, 0) will be used instead.
For example, when you set vm.someData = 'new value', the component will not re-render immediately. When the queue is flushed, the component is updated on the next "tick" when the event loop queue is cleared. Most of the time we don't need to worry about this process, but if you want to do something after the DOM state is updated, it can be a bit tricky. While Vue.js generally encourages developers to think in a "data-driven" way and avoid touching the DOM directly, there are times when we really need to do that. To wait for Vue to finish updating the DOM after the data changes, you can use Vue.nextTick(callback) immediately after the data changes. This callback function will be called after the DOM update is completed.
3. NextTick source code analysis
Function
Vue.nextTick is used to delay the execution of a piece of code, it accepts 2 parameters (callback function and the context in which the callback function is executed). If no callback function is provided, a promise object will be returned.
Source code
/** * Defer a task to execute it asynchronously. */ export const nextTick = (function () { const callbacks = [] let pending = false let timerFunc function nextTickHandler () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } } // the nextTick behavior leverages the microtask queue, which can be accessed // via either native Promise.then or MutationObserver. // MutationObserver has wider support, however it is seriously bugged in // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It // completely stops working after triggering a few times... so, if native // Promise is available, we will use it: /* istanbul ignore if */ if (typeof Promise !== 'undefined' && isNative(Promise)) { var p = Promise.resolve() var logError = err => { console.error(err) } timerFunc = () => { p.then(nextTickHandler).catch(logError) // 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) } } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]' )) { // use MutationObserver where native Promise is not available, // e.g. PhantomJS, iOS7, Android 4.4 var counter = 1 var observer = new MutationObserver(nextTickHandler) var textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } } else { // fallback to setTimeout /* istanbul ignore next */ timerFunc = () => { setTimeout(nextTickHandler, 0) } } return function queueNextTick (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() } if (!cb && typeof Promise !== 'undefined') { return new Promise((resolve, reject) => { _resolve = resolve }) } } })()
First of all, let’s understand the three important variables defined in nextTick.
callbacks: used to store all callback functions that need to be executed
pending: used to mark whether the callback function is being executed
timerFunc: used to trigger the execution of the callback function
Next, learn about the nextTickHandler() function.
function nextTickHandler () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } }
This function is used to execute all callback functions stored in callbacks.
The next step is to assign the trigger method to timerFunc.
First determine whether promise is natively supported. If so, use promise to trigger the execution of the callback function;
Otherwise, if MutationObserver is supported, instantiate an observer object and observe changes in the text node. When, all callback functions are triggered to be executed.
If neither is supported, use setTimeout to set the delay to 0.
Finally is the queueNextTick function. Because nextTick is an immediate function, the queueNextTick function is a returned function that accepts parameters passed in by the user and is used to store callback functions in callbacks.
#The above picture is the entire execution process. The key lies in timeFunc(), which plays the role of delayed execution.
From the above introduction, we can know that there are three implementation methods of timeFunc().
Promise
MutationObserver
setTimeout
其中Promise和setTimeout很好理解,是一个异步任务,会在同步任务以及更新DOM的异步任务之后回调具体函数。
下面着重介绍一下MutationObserver。
MutationObserver是HTML5中的新API,是个用来监视DOM变动的接口。他能监听一个DOM对象上发生的子节点删除、属性修改、文本内容修改等等。
调用过程很简单,但是有点不太寻常:你需要先给他绑回调:
var mo = new MutationObserver(callback)
通过给MutationObserver的构造函数传入一个回调,能得到一个MutationObserver实例,这个回调就会在MutationObserver实例监听到变动时触发。
这个时候你只是给MutationObserver实例绑定好了回调,他具体监听哪个DOM、监听节点删除还是监听属性修改,还没有设置。而调用他的observer方法就可以完成这一步:
var domTarget = 你想要监听的dom节点 mo.observe(domTarget, { characterData: true //说明监听文本内容的修改。 })
在nextTick中 MutationObserver的作用就如上图所示。在监听到DOM更新后,调用回调函数。
其实使用 MutationObserver的原因就是 nextTick想要一个异步API,用来在当前的同步代码执行完毕后,执行我想执行的异步回调,包括Promise和 setTimeout都是基于这个原因。其中深入还涉及到microtask等内容,暂时不理解,就不深入介绍了。
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
The above is the detailed content of Detailed introduction to nextTick method in Vue. For more information, please follow other related articles on the PHP Chinese website!