您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關nextTick怎么在vue中使用,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
父組件:
<template> ..... <pop ref="pop" :name="name"/> </template> <script> export default { ..... created() { .... // 請求數據,并從接口獲取數據 Data.get({ url: xxxx, success: (data) => { // 問題出現在這里,我們賦值以后直接調用show方法,去展現,show方法調用的同時上報數據,而上報的數據這個時候還未更新到子組件 this.name = data.name this.$refs.pop.show() } }) } } </script>
子組件
<template> <div v-show="isShow"> ...... </div> </template> <script> export default { ..... props: ['name'], methods: { show() { this.isShow = true // 上報 Report('xxx', {name: this.name}) } } } </script>
問題分析:
原因vue官網上有解析( cn.vuejs.org/v2/guide/re… )
可能你還沒有注意到,Vue 異步執行 DOM 更新。只要觀察到數據變化,Vue 將開啟一個隊列,并緩沖在同一事件循環中發生的所有數據改變。如果同一個 watcher 被多次觸發,只會被推入到隊列中一次。這種在緩沖時去除重復數據對于避免不必要的計算和 DOM 操作上非常重要。然后,在下一個的事件循環“tick”中,Vue 刷新隊列并執行實際 (已去重的) 工作。Vue 在內部嘗試對異步隊列使用原生的 Promise.then 和 MessageChannel,如果執行環境不支持,會采用 setTimeout(fn, 0) 代替。
這句話就是說,當我們在父組件設置this.name=name的時候,vue并不會直接更新到子組件中(dom的更新也一樣未立即執行),而是把這些更新操作全部放入到一個隊列當中,同個組件的所有這些賦值操作,都作為一個watcher的更新操作放入這個隊列當中,然后等到事件循環結束的時候,一次性從這個隊列當中獲取所有的wathcer執行更新操作。在我們這個例子當中,就是我們在調用show的時候,實際上,我們的this.name=name并未真正執行,而是被放入隊列中。vue的這種做法是基于優化而做的,毋庸置疑,不然我們如果有n多個賦值vue就執行n多個dom更新,那效率將會非常的低效和不可取的。
下文中的更新操作指對data的值進行更新的操作,在vue中,都會被放入隊列異步執行。
解決方案:
1、 使用nextTick來延遲執行show方法(籠統得說,執行所有需要在數據真正更新后的操作
通過上面的分析我們知道,我們的所有的對vue實例的更新操作,都會先被放入一個隊列當中,延遲異步執行,這些異步操作,要么是microtask,要么是macrotask(是microtask還是macroktask取決于環境,nextTick的源碼中有所體現),根據事件循環機制,先入隊列的先執行,所以如果我們在nextTick當中執行操作就會變成這樣。
2、 使用setTimeout來延遲執行show方法,原理同上
所以我們的解決方法可以是:
this.name = data.name setTimeout(() => { this.$refs.pop.show() })
或者
this.name = data.name this.$nextTick(() => { this.$refs.pop.show() })
nextTick的實現原理
其實nextTick的實現原理是挺簡單的,簡單點說,就是實現異步,通過不同的執行環境,用不同的方式來實現,保證nextTick里面的回調函數能夠異步執行。為什么要這么做呢?因為vue對dom的更新也是異步的呀。
下面貼出源碼:
/** * 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 }) } } })()
首先我們看到這個是利用了閉包的特性,返回queueNextTick,所以我們實際調用的nextTick其實就是調用queueNextTick,一調用這個方法,就會把nextTick的回調放入隊列callbacks當中,等到合適的時機,會將callbacks中的所有回調取出來執行,以達到延遲執行的目的。為啥要用閉包呢,我覺得有兩個原因:
1、共享變量,比如callbacks、pending和timerFunc。
2、避免反復判斷,即是避免反復判斷timerFunc是利用Promise還是利用MutationObserver或是setTimeout來實現異步,這是函數柯里化的一種運用。
這里有兩個最主要的方法需要解釋下:
1、 nextTickHandler
這個函數,就是把隊列中的回調,全部取出來執行,類似于microtask的任務隊列。我們通過調用Vue.$nextTick就會把回調全部放入這個隊列當中,等到要執行的時候,調用nextTickHandler全部取出來執行。
2、 timerFunc
這個變量,它的作用就是通過Promise/Mutationobserver/Settimeout把nextTickHandler放入到真正的任務隊列當中,等到事件循環結束,就從任務隊列當中取出nextTickHandler來執行,nextTickHandler一執行,callbacks里面的所有回調就會被取出來執行來,這樣就達到來延遲執行nextTick傳的回調的效果。
通過這個簡單的源碼分析,我們可以得出兩個結論
1、nextTick會根據不同的執行環境,異步任務可能為microtask或者macrotask,而不是固定不變的。所以,如果你想讓nextTick里面的異步任務統統看成是microtask的話,你會遇到坑的。
2、nextTick的并不能保證一定能獲取得到更新后的dom,這取決于你是先進行數據賦值還是先調用nextTick。比如:
new Vue({ el: '#app', data() { return { id: 2 } }, created() { }, mounted() { this.$nextTick(() => { console.log(document.getElementById('id').textContent) // 這里打印出來的是2,因為先調用了nextTick }) this.id = 3 } })
看完上述內容,你們對nextTick怎么在vue中使用有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。