摘要:對綁定的事件和屬性等進行處理,其中包含指令。有專門的方法來處理指令,這個方法是,其作用,獲取指令鉤子,和對不同鉤子進行不同處理。
寫文章不容易,點個贊唄兄弟
專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧
研究基于 Vue版本 【2.5.17】
如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關注公眾號也可以吧
【Vue原理】Directives - 源碼版
咦,上一篇我們已經講過白話版啦,主要的邏輯大家應該也清楚了的,今天我們就直接開干源碼。有興趣讀源碼的同學,希望對你們有幫助哦~
沒看過白話版的,還是先別看源碼版了,那么多代碼看了估計會懵逼...
首先,上一篇說過,Vue 會在DOM 創建之后,插入父節點之前。對DOM綁定的事件和屬性等進行處理,其中包含指令。
Vue 有專門的方法來處理指令,這個方法是 updateDirectives,其作用,獲取指令鉤子,和對不同鉤子進行不同處理。
updateDirectives 的源碼不是很短,其中還涉及其他方法,不打算一次性放出來,打算一塊一塊分解地講,所以 源碼會被我分成很多塊
今天我們以兩個問題開始
1、怎么獲取到設置的指令鉤子
2、內部怎么調用鉤子函數
還有,模板上指令會被解析成數組,比如下面這個模板
會被解析成下面的渲染函數,看下其中的 directives,這就是指令被解析成的終極形態了。下面 updateDirectives 方法處理指令,處理的就是這個數組
with(this) { return _c("div", { directives: [{ name: "test", rawName: "v-test" },{ name: "test2", rawName: "v-test2" }] }) }怎么獲取設置的指令鉤子
在 updateDirectives 中,處理的是指令的鉤子,那么第一步肯定是要先獲取鉤子啊,不要處理個錘子。
function updateDirectives(oldVnode, vnode) { // 獲取舊節點的指令 var oldDirs = normalizeDirectives$1( oldVnode.data.directives, oldVnode.context); // 獲取新節點的指令 var newDirs = normalizeDirectives$1( vnode.data.directives, vnode.context); }
你也看到了,上面的源碼中有一個 normalizeDirectives$1,他就是獲取鉤子的幕后黑手。
先看作用,再看源碼
1、遍歷本節點所有的指令,逐個從組件中獲取
2、把獲取的鉤子添加到 遍歷到的當前指令上
function normalizeDirectives$1(dirs, vm) { var res = {}; var i, dir; for (i = 0; i < dirs.length; i++) { dir = dirs[i]; res[dir.name] = dir; dir.def = vm.$options["directives"][dir.name]; } return res }
最后返回的是什么呢,舉個例子看下
比如開始處理的指令數組是下面
directives: [{ name: "test", rawName: "v-test" }]
v-test 的鉤子函數是
new Vue({ directives:{ test:{ bind(){...}, inserted(){...}, .... 等其他鉤子 } } })
經過 normalizeDirectives$1 ,就會返回下面這個
directives: [{ name: "test", rawName: "v-test", def:{ bind(){...}, .... 等其他鉤子 } }]
好的,拿到了鉤子,那我們下一步就是要處理鉤子了!
怎么調用鉤子哈哈,看過白話版的,就知道這里不同的鉤子的處理流程大概是什么樣子,今天,這里是不會重復去描述啦,大概放些源碼,供大家去學習。
bind 、update、unbind 都是直接觸發的,沒有什么好講的,觸發的代碼我已經標藍了
function updateDirectives(oldVnode, vnode) { // 如果舊節點為空,表示這是新創建的 var isCreate = oldVnode === emptyNode; // 如果新節點為空,表示要銷毀 var isDestroy = vnode === emptyNode; var key, oldDir, dir; for (key in newDirs) { oldDir = oldDirs[key]; dir = newDirs[key]; if (!oldDir) { dir.def.bind(vnode.elm, dir, vnode, oldVnode, isDestroy) ...inserted 處理 } else { dir.def.update(vnode.elm, dir, vnode, oldVnode, isDestroy) ...componentUpdated處理 } } ... ...inserted 和 componentUpdated 處理 ... if (!isCreate) { for (key in oldDirs) { if (!newDirs[key]) { oldDirs[key].def.unbind(vnode.elm, dir, vnode, oldVnode, isDestroy) } } } }
重點我們講 inserted 和 componentUpdated 兩個鉤子就好了
1、insertedinserted 是在DOM 插入父節點之后才觸發的,而 處理 inserted 是在 DOM 插入之前,所有這里不可能直接觸發,只能是先保存起來,等到 節點被插入之后再觸發
所以,inserted 分為 保存和 執行兩個步驟,我們按兩個步驟來看源碼
保存鉤子
下面保存 inserted 鉤子的源碼可以看成三步
1、保存進數組 dirsWithInsert
2、組裝成函數 callInsert
3、合并到 insert 鉤子
function updateDirectives(oldVnode, vnode) { // 如果舊節點為空,表示這是新創建的 var isCreate = oldVnode === emptyNode; var dirsWithInsert = []; var key, oldDir, dir; for (key in newDirs) { oldDir = oldDirs[key]; dir = newDirs[key]; if (!oldDir) { if (dir.def && dir.def.inserted) { dirsWithInsert.push(dir); } } } if (dirsWithInsert.length) { var callInsert = function() { for (var i = 0; i < dirsWithInsert.length; i++) { callHook$1(dirsWithInsert[i], "inserted", vnode, oldVnode); } }; if (isCreate) { // 把callInsert 和本節點的 insert 合并起來 vnode.data.hook["insert"] = callInsert } else { callInsert(); } } }
執行鉤子
通過白話版的測試我們已經知道,inserted 鉤子是所有節點都插入完畢之后才觸發的,而不是插入一個節點就觸發一次
現在我們從頭探索這個執行的流程
頁面初始化,調用 patch 處理根節點,開始插入頁面的步驟,其中會不斷遍歷子節點
function patch(oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) { var insertedVnodeQueue=[] if(需要更新){...省略...} // 不是更新,而是頁面初始化 else{ // 其中會不斷地遍歷子節點,遞歸秭歸等.... createElm(vnode,insertedVnodeQueue,...); invokeInsertHook(vnode, insertedVnodeQueue); } return vnode.elm }
上面的 createElm 會創建本節點以及其后代節點,然后插入到父節點中
等到 createElm 執行完,所有節點都已經插入完畢了
function createElm( vnode,insertedVnodeQueue, parentElm,refElm ){ vnode.elm = document.createElement(vnode.tag); // 不斷遍歷子節點,遞歸調用 createElm if (Array.isArray(children)) { for (var i = 0; i < children.length; ++i) { createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i); } } // 處理本節點的事件,屬性等,其中包含對指令的處理 invokeCreateHooks(vnode, insertedVnodeQueue); // 插入 本DOM 到父節點中 insert(parentElm, vnode.elm, refElm); }
此時,invokeInsertHook 開始執行,invokeInsertHook 是統一調用 inserted 鉤子的地方。
function invokeInsertHook(vnode, insertedVnodeQueue) { for (var i = 0; i < insertedVnodeQueue.length; ++i) { insertedVnodeQueue[i].data.hook.insert(queue[i]); } }
因為 patch 只會在 根節點調用一次,invokeInsertHook 只在 patch 中調用
所以 inserted 才會在所有節點都插入父節點完畢之后,統一觸發,而不是一個個來。
收集節點
invokeCreateHooks 用于調用各種函數處理事件、屬性、指令等
也是在這里添加節點到 insertedVnodeQueue
function invokeCreateHooks(vnode, insertedVnodeQueue) { // 其中會執行 updateDirectives... for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) { cbs.create[i$1](emptyNode, vnode); } i = vnode.data.hook; // 保存含有 insert 函數的節點 if (isDef(i) && isDef(i.insert)) { insertedVnodeQueue.push(vnode); } }
然后,執行 inserted 的源碼可以看成 兩步 1、把所有含有 insert 函數的節點,保存到 insertedVnodeQueue 2、所有節點插入完畢,遍歷 insertedVnodeQueue ,執行其中節點的 insert 函數 注意,insert 不是 inserted 哦,只是邏輯上 insert 包含 inserted 大概的函數調用邏輯如下2、componentUpdated
這個鉤子和 inserted 差不多,只是執行的流程不一樣
同樣分為保存和執行兩段源碼
保存鉤子
function updateDirectives(oldVnode, vnode) { // 如果舊節點為空,表示這是新創建的 var isCreate = oldVnode === emptyNode; var dirsWithPostpatch = []; var key, oldDir, dir; for (key in newDirs) { oldDir = oldDirs[key]; dir = newDirs[key]; if (!oldDir) {....} else { if (dir.def && dir.def.componentUpdated) { dirsWithPostpatch.push(dir); } } } // 把指令componentUpdated的函數 和本節點的 postpatch 合并起來 if (dirsWithPostpatch.length) { vnode.data.hook["postpatch"] = function() { for (var i = 0; i < dirsWithPostpatch.length; i++) { callHook$1(dirsWithPostpatch[i], "componentUpdated", vnode, oldVnode); } }); } }
執行鉤子
componentUpdated 鉤子是更新一個節點就馬上執行的
更新一個節點的意思是包括其內部的子節點的
那內部的流程是怎么樣的呢?
同樣,更新就是更新節點,也會調用 patch
function patch(oldVnode, vnode) { if(需要更新){ patchVnode(oldVnode, vnode) } return vnode.elm } function patchVnode(oldVnode, vnode){ // 遞歸調用 patchVnode 更新子節點 updateChildren(oldVnode, vnode,.....); // 執行本節點的 postpatch if (isDef(i = data.hook) && isDef(i = i.postpatch)) { i(oldVnode, vnode); } }
舉個栗子走下流程
需要更新的時候,調用順序
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/105267.html
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理源碼版之節點數據拼接上一篇我們 寫文章不容易,點個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究...
寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關注公眾號也可以吧 【Vue原理】Mixins - 源碼版 今天探索的是 mixins 的源碼,mixins 根據不同的選項類型會做不同的處理 篇幅會有些長,...
摘要:首先,兄弟,容我先說幾句涉及源碼很多,篇幅很長,我都已經分了上下三篇了,依然這么長,但是其實內容都差不多一樣,但是我還是毫無保留地給你了。 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關注公眾號也...
摘要:還原的難度就在于變成模板了,因為其他的什么等是原封不動的哈哈,可是直接照抄最后鑒于本人能力有限,難免會有疏漏錯誤的地方,請大家多多包涵,如果有任何描述不當的地方,歡迎后臺聯系本人,有重謝 寫文章不容易,點個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理源碼版之創建組件今天就要開啟我 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于...
閱讀 818·2023-04-25 20:18
閱讀 2100·2021-11-22 13:54
閱讀 2543·2021-09-26 09:55
閱讀 3910·2021-09-22 15:28
閱讀 2980·2021-09-03 10:34
閱讀 1716·2021-07-28 00:15
閱讀 1642·2019-08-30 14:25
閱讀 1286·2019-08-29 17:16