摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關(guān)注公眾號也可以吧原理源碼版之節(jié)點數(shù)據(jù)拼接上一篇我們
寫文章不容易,點個贊唄兄弟
專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧
研究基于 Vue版本 【2.5.17】
如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關(guān)注公眾號也可以吧
【Vue原理】Compile - 源碼版 之 generate 節(jié)點數(shù)據(jù)拼接
上一篇我們講了不同節(jié)點的拼接,這一篇需要詳細記錄的是 節(jié)點數(shù)據(jù)的拼接
節(jié)點數(shù)據(jù),包括有 props,attrs,事件等
上一篇我們在 genElement 中看到過,每個節(jié)點都需要去拼接節(jié)點數(shù)據(jù),使用的就是下面源碼中的 genData$2 這個方法
function genElement() { .....處理其他類型的節(jié)點 var data = genData$2(el, state); var children = genChildren(el, state); code = `_c("${el.tag}", $ { data ? ("," + data) : "" }, $ { children ? ("," + children) : "" })` }genData$2
這個函數(shù)的源碼有點長,但是不用怕,都是處理各種屬性的判斷,所以內(nèi)容大約一致,不過里面涉及到具體的方法,會具體看
來吧,先過一遍把
function genData$2(el, state) { var data = "{"; // 先解析指令 var dirs = genDirectives(el, state); // 拼接上解析得到的指令字符串 if(dirs) { data += dirs + ","; } // 帶有 is 綁定的組件,直接使用組件則沒有 if(el.component) { data += `tag: ${el.tag} , ` } // 上一篇說過的,dataGenFns 包含處理style,class的函數(shù) for(var i = 0; i < state.dataGenFns.length; i++) { data += state.dataGenFns[i](el); } // 全部屬性 if(el.attrs) { data += ` attrs:{ ${genProps(el.attrs)) } ,` } // 原生屬性 if(el.props) { data += ` domProps:{ ${genProps(el.props)} }, ` } // 事件 if(el.events) { data += genHandlers(el.events, false) + ","; } // 原生事件 if(el.nativeEvents) { data += genHandlers(el.nativeEvents, true) + ","; } // 沒有作用域的 slot if( el.slotTarget && !el.slotScope ) { data += ` slot: ${ el.slotTarget } ,` } // 作用域slot if(el.scopedSlots) { data += genScopedSlots(el.scopedSlots, state) + ","; } // 組件使用 v-model if(el.model) { data += `model:{ value:${el.model.value}, callback:${el.model.callback}, expression:${el.model.expression}, },` } data = data.replace(/,$/, "") + "}"; return data }
首先這個方法,最終返回的是一個對象的序列化字符串,比如這樣
" { a:b , c:d } "
所以頭尾都會 加上大括號,然后屬性拼接xx:yy 的形式
下面我們就來一個個看對于不同屬性的處理
拼接指令function genDirectives(el, state) { var dirs = el.directives; if (!dirs) return var res = "directives:["; var hasRuntime = false; var i, l, dir, needRuntime; for (i = 0, l = dirs.length; i < l; i++) { dir = dirs[i]; needRuntime = true; // 獲取到特定的 Vue 指令處理方法 var gen = state.directives[dir.name]; // 如果這個函數(shù)存在,證明這個指令是內(nèi)部指令 if (gen) { needRuntime = gen(el, dir); } if (needRuntime) { hasRuntime = true; res += `{ name: ${dir.name}, rawName: ${dir.rawName} ${ dir.value ? ",value:" + dir.value +", expression:" + dir.value : "" } ${ dir.arg ? ( ",arg:" + dir.arg ) : "" } ${ dir.modifiers ? (",modifiers:" + JSON.stringify(dir.modifiers)) : "" } }, ` } } if (hasRuntime) { return res.slice(0, -1) + "]" } }
首先呢,我們要了解這個方法會返回什么字符串,比如
就會返回這樣的字符串
`directives:[{ name:"test", rawName:"v-test:a.b.c", value:222, expression:"arr", arg:"a", modifiers:{"b":true,"c":true} }]`
每一個指令,都會解析成一個對象字符串,然后拼接在字符串?dāng)?shù)組里面
那么下面就來詳細記錄幾個可能疑惑的點
函數(shù)中出現(xiàn)的 state.directives在上面文章中的 CodegenState 中,我們有寫過這個
state.directives 是一個數(shù)組,包含了 Vue內(nèi)部指令的處理函數(shù),如下
v-on,v-bind,v-cloak,v-model ,v-text,v-html
函數(shù)中的變量 needRuntime一個標(biāo)志位,表示是否需要把指令的數(shù)據(jù)解析成一個 對象字符串,像這樣
`{ name:xxx, rawName:xxx, value:xxx, expression:xx, arg:xx, modifiers:xx }`
也就是說,這個指令是否需要被拼接成 render 字符串中
那么什么指令需要,什么指令不需要呢?自定義指令,都需要被解析,拼接在 render 字符串中
但是 Vue 的內(nèi)部指令,有的用,有的不用,所以就搞出一個 needRunTime 來進行判斷
Vue 的指令,先要獲取到特定的處理方法,賦值給 gen
gen 處理完返回 true,則表示需要 拼接上render,返回 false 或者不返回,則表示不需要拼接上
比如,v-model 指令的數(shù)據(jù)就需要拼接上 render,而 v-text,v-html 則不用
看下面的例子
比如上面的模板拼接成下面的字符串,發(fā)現(xiàn) v-html 并沒有出現(xiàn)在 directives 那個字符串?dāng)?shù)組中
`_c("div",{ directives:[{ name:"model", rawName:"v-model", value:arr, expression:"arr" }], domProps:{ "innerHTML":_s() } })`函數(shù)中的變量 hasRuntime
一個標(biāo)志位,表示是否需要把 return 指令字符串
genDirectives 處理的是一個指令數(shù)組,當(dāng)數(shù)組為空的時候,并不會有返回值
那么 render 字符串就不會 存在 directive 這一段字符串
如果指令不為空,那么 hasRunTime 設(shè)為 true,需要返回字符串
并且在 字符串尾部加上 ] , 這樣字符串?dāng)?shù)組就完整了
拼接組件這里的解析組件,解析的是帶有 is 屬性的綁定組件
很簡單,就是拼接上一個 tag 的屬性就ok 了
看例子
原有的標(biāo)簽名,被拼接在 tag 后面
` _c("test",{tag:"div"}) `拼接樣式
上篇文章也說過,state.dataGenFns 是一個數(shù)組
存放的是兩個函數(shù),一個是解析 class ,一個是解析 style 的
這里放下其中的源碼,非常的簡單
解析 classfunction genData(el) { var data = ""; if (el.staticClass) { data += "staticClass:" + el.staticClass + ","; } if (el.classBinding) { data += "class:" + el.classBinding + ","; } return data }解析style
function genData$1(el) { var data = ""; if (el.staticStyle) { data += "staticStyle:" + el.staticStyle + ","; } if (el.styleBinding) { data += "style:(" + el.styleBinding + "),"; } return data }
實在是太簡單的,就是直接拼接上幾個屬性而已啦
給例子就好了
`_c("div",{ staticClass:"a", class:name, staticStyle:{"height":"0"}, style:{width:0} }) `拼接屬性
屬性的拼接只有一個函數(shù),內(nèi)容也十分簡單
function genProps(props) { var res = ""; for (var i = 0; i < props.length; i++) { var prop = props[i]; res += prop.name + ":" + prop.value + ","; } return res.slice(0, -1) }
你可以看到,雖然只有一個方法,但是在 genData$2 中,拼接的結(jié)果會有兩種
拼接到 el.attr 拼接到 el.props為什么會拼接到不同的地方?
因為看的是你屬性 放的位置
如果你的屬性位置是 標(biāo)簽上,那么就會拼接到 attr 中
如果你的屬性位置是在 dom 上,那么就被拼接到 domProps 中
舉個例子
比如下面的模板,bbb 就是放在 標(biāo)簽上,aaa 就是放在 DOM 上
拼接的結(jié)果就是
` _c("div",{ attrs:{"bbb":"bbb"}, domProps:{"aaa":11} }) `
頁面標(biāo)簽看不到 aaa
可以在 dom 屬性中找到 aaa
拼接事件事件的拼接,內(nèi)容很多,打算放在另一篇文章詳細記錄
事件拼接還分為兩種,原生事件和 自定義事件,只是拼接為不同字符串而已,但是處理方法一樣
方法中涉及到各種 修飾符,哈哈,想知道到底為什么能寫出這么方便的 api 呢哈哈
綁定按鍵,阻止默認(rèn)事件,直接這么寫就行了
@keyup.enter.prevent="xxx"
歡迎觀看下篇文章
拼接普通Slot就是直接拼接上 slot 這個屬性
` _c("test",[_c("span",{ attrs:{"slot":"name"}, slot:"name" })] ) `
如果組件有slot,沒有 slot 這個屬性,那么就不會拼接上slot,后面會直接給個默認(rèn)名字 “default”
拼接作用域Slotfunction genScopedSlots(slots, state) { return ` scopedSlots:_u([${ Object.keys(slots).map(key =>{ return genScopedSlot(key, slots[key], state) }) .join(",") }]) ` } function genScopedSlot(key, el, state) { var fn = ` function(${el.slotScope}){ return ${ el.tag === "template" ? genChildren(el, state) : genElement(el, state) } } ` return `{ key:${key} , fn: ${fn} }` }
這個處理作用域 slot 的函數(shù)看起來好像有一點復(fù)雜,但是其實就是紙老虎
不怕,先看一個實例
拼接成字符串,是這樣的
` _c("div",{ scopedSlots:_u([{ key:"heder", fn:function(arr){return _c("div")} }]) }) `
這個函數(shù)遍歷的是 el.scopeSlots 這個數(shù)組,或許你不知道這個數(shù)組是什么內(nèi)容?
同樣給個例子,這里有兩個 slot
經(jīng)過 parse 解析之后成一個 ast,是這樣的
{ tag:"test", scopedSlots:[{ slotScope: "arr" slotTarget: ""a"" tag: "div" },{ slotScope: "arr" slotTarget: ""b"" tag: "div" }] }
沒錯,遍歷的就是上面對象里面的 scopedSlots 數(shù)組,數(shù)組中的每一項都是一個多帶帶的 slot
然后會使用 genScopeSlot 去多帶帶處理一下,上面有放出源碼
處理完之后,形成一個新的數(shù)組,genScopeSlot 也沒什么好說的
拼接分類型,需要判斷 slot 位置的標(biāo)簽是不是 template
如果是template,那么他的真實slot 是 template 的子節(jié)點,直接獲取他的子節(jié)點
如果不是template,那么本身就是真實的slot
因為 template 是Vue 自帶的一個 模板節(jié)點,是不存在的
拼接組件VModel沒錯,這里的 model,只是屬于 組件的 v-model
if (el.model) { data += `model: { value: $ { el.model.value }, callback: $ { el.model.callback }, expression: $ { el.model.expression }, }, ` }
官網(wǎng)說了這個是怎么用的
一個組件上的 v-model 默認(rèn)會利用名為 value 的 prop 和名為 input 的事件
也就是說,起始就是給組件傳了一個 value,綁定了一個事件 input
也沒有什么好講的,記錄下組件的 v-model 是這么拼接就好了
例子經(jīng)過 parse 解析,得到 ast
{ tag: "test", model:{ callback: "function ($$v) {num=$$v}" expression: ""num"" value: "num" } }
拼接成字樣變成字符串了
` _c("test",{ model:{ value:num, callback:function ($$v) {num=$$v}, expression:"num" } }) `舉個栗子
屬性拼接呢,我們就講完了,最后我們來看一個例子吧
下面這個模板,我們把它拼接起來
解析成下面這個 render 字符串,看懂了,你就掌握了 generate 的內(nèi)容了
以后你就可以去看別人用Vue 寫的打包后的代碼了
甚至,你可以手動還原他,如果你閑得很,你可以自己寫個方法,傳入render 字符串,自動還原成 template 模板
` _c("div", { attrs: { "b": "2" }, domProps: { "a": 11 } },[ _c("test", { scopedSlots: _u([{ key: "a", fn: function(arr) { return _c("strong") } }]), model: { value: (num), callback: function($$v) { num = $$v }, expression: "num" } },[_c("span")]) ]) `最后
鑒于本人能力有限,難免會有疏漏錯誤的地方,請大家多多包涵,如果有任何描述不當(dāng)?shù)牡胤剑瑲g迎后臺聯(lián)系本人,領(lǐng)取紅包
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/106697.html
摘要:還原的難度就在于變成模板了,因為其他的什么等是原封不動的哈哈,可是直接照抄最后鑒于本人能力有限,難免會有疏漏錯誤的地方,請大家多多包涵,如果有任何描述不當(dāng)?shù)牡胤?,歡迎后臺聯(lián)系本人,有重謝 寫文章不容易,點個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5.17】 如果你覺得排版...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關(guān)注公眾號也可以吧原理白話版終于到了要講白話的時候了 寫文章不容易,點個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關(guān)注公眾號也可以吧原理源碼版之拼接綁定的事件今天我們 寫文章不容易,點個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究...
摘要:一旦我們檢測到這些子樹,我們可以把它們變成常數(shù),這樣我們就不需要了在每次重新渲染時為它們創(chuàng)建新的節(jié)點在修補過程中完全跳過它們。否則,吊裝費用將會增加好處大于好處,最好總是保持新鮮。 寫文章不容易,點個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,...
摘要:頁面這個實例,按理就需要解析兩次,但是有緩存之后就不會理清思路也就是說,其實內(nèi)核就是不過是經(jīng)過了兩波包裝的第一波包裝在中的內(nèi)部函數(shù)中內(nèi)部函數(shù)的作用是合并公共和自定義,但是相關(guān)代碼已經(jīng)省略,另一個就是執(zhí)行第二波包裝在中,目的是進行緩存 寫文章不容易,點個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 ...
閱讀 961·2019-08-30 14:24
閱讀 999·2019-08-30 14:13
閱讀 1807·2019-08-29 17:21
閱讀 2694·2019-08-29 13:44
閱讀 1667·2019-08-29 11:04
閱讀 453·2019-08-26 10:44
閱讀 2573·2019-08-23 14:04
閱讀 915·2019-08-23 12:08