路由場景下父子元件的生命週期順序來個刨根問底
大家中秋假期快樂,假期分享一些原理設計文章給大家
原創不易,歡迎轉發,一起學習(凌晨寫的,不容易哈,收藏或者點個贊吧)
在常見的單頁應用中,我們都會有一個根 App.vue 檔案,裡面放置一個 router-view 然後配置路由來切換.
很多人在子父元件巢狀關係下的生命週期鉤子函式如何應用,誰先誰後(比如哪個用來發送請求,資料傳遞)等有所疑問。
本文聚焦 mounted
事件(需要 created
的可以留言哈),先拋結論:
子元件一層一層往外觸發,最終觸發根 App.vue 的 mounted
驗證的做法很簡單:
你只需要在每一個元件裡面的 mounted 增加列印日誌就可以看到了,我們具體來看看設計原理
現在假設我們配置了路由:
一級是 /user/:id 二級是 profile
const router = new VueRouter({ routes: [ { path: '/user/:id', component: User, children: [ { // 當 /user/:id/profile 匹配成功, // UserProfile 會被渲染在 User 的 <router-view> 中 path: 'profile', component: UserProfile } ] } ] })
先看一下所有的 mounted
最終在各自元件裡面是如何被呼叫
的:
通過 vm.$options
獲取元件內部的配置,然後通過 call
方法
下面的我們又會遇到 vnode(所以掌握它很重要,在前面的 vue.js 原始碼原創系列 ref 與 $refs 如何關聯 也提到了一些 vnode,感興趣可以看看),就是下面 componentVNodeHooks 裡面的 insert 函式的引數
var componentVNodeHooks = {
insert: function insert (vnode) {}
}
裡面呢,第一步:從 vnode 裡面獲取 componentInstance
var componentInstance = vnode.componentInstance;
然後判斷 _isMounted
是否已經執行過 mounted (很常用的狀態二次確定的變數,前面的 _ 一般代表內部使用)
if (!componentInstance._isMounted) {
componentInstance._isMounted = true;
callHook(componentInstance, 'mounted');
}
上面我們就用到了 callHook
函數了,傳入的第二個引數也正是本文討論的生命週期的 mounted
再往後有一個 invokeInsertHook 函式
function invokeInsertHook (vnode, queue, initial) {
}
注意一下原始碼裡面的註釋:
delay insert hooks for component root nodes, invoke them after the element is really inserted
設定了 pendingInsert
(後面會在 initComponent
中使用),程式碼如下:
if (isTrue(initial) && isDef(vnode.parent)) {
vnode.parent.data.pendingInsert = queue;
}
內部設計:迴圈 queue 這個包含 vnode 的陣列物件,如圖所示:
注意一下標註的 data.hook
,下面的程式碼片段會使用到,也就是呼叫上面提到的 componentVNodeHooks
物件的 insert
:
for (var i = 0; i < queue.length; ++i) {
queue[i].data.hook.insert(queue[i]);
}
再往下,帶著疑問:
這個 queue 是如何生成 vnode 陣列的呢?
最開始定義一個空陣列:
var insertedVnodeQueue = [];
在剛才的 物件中還有 init
var componentVNodeHooks = {
init: function init () {
// ...
}
}
init
函式內部定義一個 child
var child = vnode.componentInstance = createComponentInstanceForVnode(...)
然後會呼叫一個 $mount
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
在函式 initComponent
中會用到之前的 pendingInsert
,而且 insertedVnodeQueue 這個陣列
物件會呼叫 push
插入元素
function initComponent (vnode, insertedVnodeQueue) {
if (isDef(vnode.data.pendingInsert)) {
insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert);
vnode.data.pendingInsert = null;
}
}
在函式 invokeCreateHooks
內部insertedVnodeQueue 這個陣列
物件會呼叫 push
插入元素
function invokeCreateHooks (vnode, insertedVnodeQueue) {
i = vnode.data.hook; // Reuse variable
if (isDef(i)) {
if (isDef(i.insert)) {
insertedVnodeQueue.push(vnode);
}
}
}
在函式 mountComponent
內部當 vm.$vnode 為 null 也會呼叫 callHook
,第二個引數傳入 mounted
function mountComponent () {
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, 'mounted');
}
}