您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“vue中的mounted鉤子怎么用”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“vue中的mounted鉤子怎么用”這篇文章吧。
注:閱讀本文需要對vue的patch流程有較清晰的理解,如果不清楚patch流程,建議先了解清楚這個流程再閱讀本文,否則可能會感覺云里霧里。
聊之前我們先看一個場景
<div id="app"> <aC /> <bC /> </div>
如上所示,App.vue文件里面有兩個子組件,互為兄弟關系
組件里面自各有created和mounted兩個生命周期鉤子,a表示組件名 C是created的縮寫
// a組件 created() { console.log('aC') }, mounted() { debugger console.log('aM') }, // b組件 created() { console.log('bC') }, mounted() { debugger console.log('bM') },
請問打印順序是什么?各位讀者可以先腦補一下,后面看看對不對。
如果對vue patch流程比較熟悉的讀者,可能會認為順序是aC→aM→bC→BM,也就是a組件先創建,再掛載,然后到b組件重復以上流程。因為從patch的方法里面可以知道,組件created后,再走到insert進父容器的過程,是一個同步的流程,只有這個流程走完后,才會遍歷到b組件,走b組件的渲染流程。
實際上瀏覽器打印出來的順序是aC→bC→aM→bM,也就是兩個created先執行,才到mounted執行,和上面的分析相悖。這里先說原因,子組件從created到insert進父容器的過程還是同步的,但是insert進父容器后,也可以理解為子組件mounted,并沒有馬上調用mounted生命周期鉤子。下面從源碼角度分析一下:
先大概回顧一下子組件渲染流程,patch函數調用createElm創建真實element,createElm里面通過createComponent判斷當前vnode是否組件vnode,是則進入組件渲染流程
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) { var i = vnode.data; if (isDef(i)) { var isReactivated = isDef(vnode.componentInstance) && i.keepAlive; if (isDef(i = i.hook) && isDef(i = i.init)) { i(vnode, false /* hydrating */); } if (isDef(vnode.componentInstance)) { initComponent(vnode, insertedVnodeQueue); // 最終組件創建完后會走到這里 把組件對應的el插入到父節點 insert(parentElm, vnode.elm, refElm); if (isTrue(isReactivated)) { reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm); } return true } } }
createComponent里面就把組件對應的el插入到父節點,最后會返回到patch調用棧,調用
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
因為子組件有vnode.parent所以會走一個分支,但是我們也看看第二個分支調用的insert是什么
function invokeInsertHook (vnode, queue, initial) { // delay insert hooks for component root nodes, invoke them after the // element is really inserted if (isTrue(initial) && isDef(vnode.parent)) { vnode.parent.data.pendingInsert = queue; } else { for (var i = 0; i < queue.length; ++i) { queue[i].data.hook.insert(queue[i]); } } }
這個insert是掛在vnode.data.hook上,在組件創建過程中,createComponent方法里面有一個調用
installComponentHooks,在這里把insert鉤子注入了。這個方法實際定義在componentVNodeHooks對象里面,可以看到這個insert里面調用了callHook(componentInstance, 'mounted'),這里實際上就是調用子組件的mounted生命周期。
insert: function insert (vnode) { var context = vnode.context; var componentInstance = vnode.componentInstance; if (!componentInstance._isMounted) { componentInstance._isMounted = true; callHook(componentInstance, 'mounted'); } if (vnode.data.keepAlive) { if (context._isMounted) { // vue-router#1212 // During updates, a kept-alive component's child components may // change, so directly walking the tree here may call activated hooks // on incorrect children. Instead we push them into a queue which will // be processed after the whole patch process ended. queueActivatedComponent(componentInstance); } else { activateChildComponent(componentInstance, true /* direct */); } } },
再來看看這個方法,子組件走第一個分支,僅僅執行了一行代碼vnode.parent.data.pendingInsert = queue , 這個queue實際是在patch 開始時候,定義的insertedVnodeQueue。這里的邏輯就是把當前的insertedVnodeQueue,掛在parent的vnode data的pendingInsert上。
function invokeInsertHook (vnode, queue, initial) { // delay insert hooks for component root nodes, invoke them after the // element is really inserted if (isTrue(initial) && isDef(vnode.parent)) { vnode.parent.data.pendingInsert = queue; } else { for (var i = 0; i < queue.length; ++i) { queue[i].data.hook.insert(queue[i]); } } } // 在patch 開始時候 定義了insertedVnodeQueue為一個空數組 var insertedVnodeQueue = [];
源碼里面再搜索insertedVnodeQueue ,可以看到有這樣一段邏輯,initComponent還是在createComponent里面調用的
function initComponent (vnode, insertedVnodeQueue) { if (isDef(vnode.data.pendingInsert)) { insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert); vnode.data.pendingInsert = null; } vnode.elm = vnode.componentInstance.$el; if (isPatchable(vnode)) { // ??注意這個方法 invokeCreateHooks(vnode, insertedVnodeQueue); setScope(vnode); } else { // empty component root. // skip all element-related modules except for ref (#3455) registerRef(vnode); // make sure to invoke the insert hook insertedVnodeQueue.push(vnode); } }
insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert) 重點看這一行代碼,把 vnode.data.pendingInsert這個數組每一項push到當前vnode的insertedVnodeQueue中,注意這里是通過apply的方式,所以是把 vnode.data.pendingInsert這個數組每一項都push,而不是push pendingInsert這個列表進去。也就是說在這里,組件把他的子組件的insertedVnodeQueue里面的item收集了,因為渲染是一個深度遞歸的過程,所有最后根組件的insertedVnodeQueue能拿到所有子組件的insertedVnodeQueue里面的每一項。
從invokeInsertHook的queue[i].data.hook.insert(queue[i]) 這一行可以看出,insertedVnodeQueue里面的item應該是vnode。源碼中搜索insertedVnodeQueue.push ,可以發現是invokeCreateHooks這個方法把當前vnode push了進去。
function invokeCreateHooks (vnode, insertedVnodeQueue) { for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) { cbs.create[i$1](emptyNode, vnode); } i = vnode.data.hook; // Reuse variable if (isDef(i)) { if (isDef(i.create)) { i.create(emptyNode, vnode); } // 把當前vnode push 到了insertedVnodeQueue if (isDef(i.insert)) { insertedVnodeQueue.push(vnode); } } }
對于組件vnode來說,這個方法還是在initComponent中調用的。
到這里就很清晰,子組件insert進父節點后,并不會馬上調用mounted鉤子,而是把組件對應到vnode插入到父vnode的insertedVnodeQueue中,層層遞歸,最終根組件拿到所有子組件的vnode,再依次循環遍歷,調用vnode的insert鉤子,從而調用了mounted鉤子。這里是先進先出的,第一個被push進去的第一個被拿出來調用,所以最深的那個子組件的mounted先執行。最后附上一張源碼調試的圖,可以清晰的看到根組件的insertedVnodeQueue是什么內容。
至于為什么vue要這樣設計,是因為掛載是先子后父的,子組件插入到了父節點,但是父節點還沒有真正插入到頁面中,如果這時候立馬調用子組件的mounted,對框架使用者來說可能會造成困惑,因為子組件調用mounted的時候并沒有真正渲染到頁面中,而且此時也肯定也無法通過document.querySelector的方式操作dom。
以上是“vue中的mounted鉤子怎么用”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。