溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

vue中的mounted鉤子怎么用

發(fā)布時間:2022-03-24 12:17:05 來源:億速云 閱讀:890 作者:小新 欄目:編程語言

這篇文章主要為大家展示了“vue中的mounted鉤子怎么用”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“vue中的mounted鉤子怎么用”這篇文章吧。

vue中的mounted鉤子怎么用

注:閱讀本文需要對vue的patch流程有較清晰的理解,如果不清楚patch流程,建議先了解清楚這個流程再閱讀本文,否則可能會感覺云里霧里。

聊之前我們先看一個場景

<div id="app">
    <aC />
    <bC />
</div>

如上所示,App.vue文件里面有兩個子組件,互為兄弟關(guān)系

組件里面自各有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')
},

請問打印順序是什么?各位讀者可以先腦補(bǔ)一下,后面看看對不對。

如果對vue patch流程比較熟悉的讀者,可能會認(rèn)為順序是aC→aM→bC→BM,也就是a組件先創(chuàng)建,再掛載,然后到b組件重復(fù)以上流程。因?yàn)閺膒atch的方法里面可以知道,組件created后,再走到insert進(jìn)父容器的過程,是一個同步的流程,只有這個流程走完后,才會遍歷到b組件,走b組件的渲染流程。

實(shí)際上瀏覽器打印出來的順序是aC→bC→aM→bM,也就是兩個created先執(zhí)行,才到mounted執(zhí)行,和上面的分析相悖。這里先說原因,子組件從created到insert進(jìn)父容器的過程還是同步的,但是insert進(jìn)父容器后,也可以理解為子組件mounted,并沒有馬上調(diào)用mounted生命周期鉤子。下面從源碼角度分析一下:

先大概回顧一下子組件渲染流程,patch函數(shù)調(diào)用createElm創(chuàng)建真實(shí)element,createElm里面通過createComponent判斷當(dāng)前vnode是否組件vnode,是則進(jìn)入組件渲染流程

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);
				// 最終組件創(chuàng)建完后會走到這里 把組件對應(yīng)的el插入到父節(jié)點(diǎn)
        insert(parentElm, vnode.elm, refElm);
        if (isTrue(isReactivated)) {
          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
        }
        return true
      }
    }
  }

createComponent里面就把組件對應(yīng)的el插入到父節(jié)點(diǎn),最后會返回到patch調(diào)用棧,調(diào)用

invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);

因?yàn)樽咏M件有vnode.parent所以會走一個分支,但是我們也看看第二個分支調(diào)用的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上,在組件創(chuàng)建過程中,createComponent方法里面有一個調(diào)用

installComponentHooks,在這里把insert鉤子注入了。這個方法實(shí)際定義在componentVNodeHooks對象里面,可以看到這個insert里面調(diào)用了callHook(componentInstance, 'mounted'),這里實(shí)際上就是調(diào)用子組件的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 */);
      }
    }
  },

再來看看這個方法,子組件走第一個分支,僅僅執(zhí)行了一行代碼vnode.parent.data.pendingInsert = queue , 這個queue實(shí)際是在patch 開始時候,定義的insertedVnodeQueue。這里的邏輯就是把當(dāng)前的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為一個空數(shù)組
var insertedVnodeQueue = [];

源碼里面再搜索insertedVnodeQueue ,可以看到有這樣一段邏輯,initComponent還是在createComponent里面調(diào)用的

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) 重點(diǎn)看這一行代碼,把 vnode.data.pendingInsert這個數(shù)組每一項(xiàng)push到當(dāng)前vnode的insertedVnodeQueue中,注意這里是通過apply的方式,所以是把 vnode.data.pendingInsert這個數(shù)組每一項(xiàng)都push,而不是push pendingInsert這個列表進(jìn)去。也就是說在這里,組件把他的子組件的insertedVnodeQueue里面的item收集了,因?yàn)殇秩臼且粋€深度遞歸的過程,所有最后根組件的insertedVnodeQueue能拿到所有子組件的insertedVnodeQueue里面的每一項(xiàng)。

從invokeInsertHook的queue[i].data.hook.insert(queue[i]) 這一行可以看出,insertedVnodeQueue里面的item應(yīng)該是vnode。源碼中搜索insertedVnodeQueue.push ,可以發(fā)現(xiàn)是invokeCreateHooks這個方法把當(dāng)前vnode push了進(jìn)去。

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); }
	     // 把當(dāng)前vnode push 到了insertedVnodeQueue
      if (isDef(i.insert)) { insertedVnodeQueue.push(vnode); }
    }
  }

對于組件vnode來說,這個方法還是在initComponent中調(diào)用的。

到這里就很清晰,子組件insert進(jìn)父節(jié)點(diǎn)后,并不會馬上調(diào)用mounted鉤子,而是把組件對應(yīng)到vnode插入到父vnode的insertedVnodeQueue中,層層遞歸,最終根組件拿到所有子組件的vnode,再依次循環(huán)遍歷,調(diào)用vnode的insert鉤子,從而調(diào)用了mounted鉤子。這里是先進(jìn)先出的,第一個被push進(jìn)去的第一個被拿出來調(diào)用,所以最深的那個子組件的mounted先執(zhí)行。最后附上一張?jiān)创a調(diào)試的圖,可以清晰的看到根組件的insertedVnodeQueue是什么內(nèi)容。

vue中的mounted鉤子怎么用

至于為什么vue要這樣設(shè)計,是因?yàn)閽燧d是先子后父的,子組件插入到了父節(jié)點(diǎn),但是父節(jié)點(diǎn)還沒有真正插入到頁面中,如果這時候立馬調(diào)用子組件的mounted,對框架使用者來說可能會造成困惑,因?yàn)樽咏M件調(diào)用mounted的時候并沒有真正渲染到頁面中,而且此時也肯定也無法通過document.querySelector的方式操作dom。

以上是“vue中的mounted鉤子怎么用”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI