寫文章不容易,點個讚唄兄弟
專一 Vue 源碼分享,文章分爲白話版和 源碼版,白話版助於理解工做原理,源碼版助於瞭解內部詳情,讓咱們一塊兒學習吧 研究基於 Vue版本 【2.5.17】html
若是你以爲排版難看,請點擊 下面連接 或者 拉到 下面關注公衆號也能夠吧express
【Vue原理】Compile - 源碼版 之 generate 節點數據拼接 api
上一篇咱們講了不一樣節點的拼接,這一篇須要詳細記錄的是 節點數據的拼接數組
節點數據,包括有 props,attrs,事件等bash
上一篇咱們在 genElement 中看到過,每一個節點都須要去拼接節點數據,使用的就是下面源碼中的 genData$2 這個方法dom
function genElement() {
.....處理其餘類型的節點
var data = genData$2(el, state);
var children = genChildren(el, state);
code = `_c('${el.tag}', $ {
data ? ("," + data) : ''
}, $ {
children ? ("," + children) : ''
})`
}
複製代碼
這個函數的源碼有點長,可是不用怕,都是處理各類屬性的判斷,因此內容大約一致,不過裏面涉及到具體的方法,會具體看函數
來吧,先過一遍把學習
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的函數
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
}
複製代碼
首先這個方法,最終返回的是一個對象的序列化字符串,好比這樣ui
" { a:b , c:d } "
複製代碼
因此頭尾都會 加上大括號,而後屬性拼接xx:yy 的形式spa
下面咱們就來一個個看對於不一樣屬性的處理
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];
// 若是這個函數存在,證實這個指令是內部指令
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}
}]`
複製代碼
每個指令,都會解析成一個對象字符串,而後拼接在字符串數組裏面
那麼下面就來詳細記錄幾個可能疑惑的點
在上面文章中的 CodegenState 中,咱們有寫過這個
state.directives 是一個數組,包含了 Vue內部指令的處理函數,以下
v-on,v-bind,v-cloak,v-model ,v-text,v-html
一個標誌位,表示是否須要把指令的數據解析成一個 對象字符串,像這樣
`{
name:xxx, rawName:xxx,
value:xxx, expression:xx,
arg:xx, modifiers:xx
}`
複製代碼
也就是說,這個指令是否須要被拼接成 render 字符串中
自定義指令,都須要被解析,拼接在 render 字符串中
可是 Vue 的內部指令,有的用,有的不用,因此就搞出一個 needRunTime 來進行判斷
Vue 的指令,先要獲取到特定的處理方法,賦值給 gen
gen 處理完返回 true,則表示須要 拼接上render,返回 false 或者不返回,則表示不須要拼接上
好比,v-model 指令的數據就須要拼接上 render,而 v-text,v-html 則不用
看下面的例子
好比上面的模板拼接成下面的字符串,發現 v-html 並無出如今 directives 那個字符串數組中
`_c('div',{
directives:[{
name:"model",
rawName:"v-model",
value:arr,
expression:"arr"
}],
domProps:{
"innerHTML":_s(<span></span>)
}
})`
複製代碼
一個標誌位,表示是否須要把 return 指令字符串
genDirectives 處理的是一個指令數組,當數組爲空的時候,並不會有返回值
那麼 render 字符串就不會 存在 directive 這一段字符串
若是指令不爲空,那麼 hasRunTime 設爲 true,須要返回字符串
而且在 字符串尾部加上 ] , 這樣字符串數組就完整了
這裏的解析組件,解析的是帶有 is 屬性的綁定組件
很簡單,就是拼接上一個 tag 的屬性就ok 了
看例子
原有的標籤名,被拼接在 tag 後面
` _c("test",{tag:"div"}) `
複製代碼
上篇文章也說過,state.dataGenFns 是一個數組
存放的是兩個函數,一個是解析 class ,一個是解析 style 的
這裏放下其中的源碼,很是的簡單
function genData(el) {
var data = '';
if (el.staticClass) {
data += "staticClass:" + el.staticClass + ",";
}
if (el.classBinding) {
data += "class:" + el.classBinding + ",";
}
return data
}
複製代碼
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}
})
`
複製代碼
屬性的拼接只有一個函數,內容也十分簡單
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 中,拼接的結果會有兩種
爲何會拼接到不一樣的地方?
由於看的是你屬性 放的位置
若是你的屬性位置是 標籤上,那麼就會拼接到 attr 中
若是你的屬性位置是在 dom 上,那麼就被拼接到 domProps 中
舉個例子
好比下面的模板,bbb 就是放在 標籤上,aaa 就是放在 DOM 上
拼接的結果就是
` _c('div',{
attrs:{"bbb":"bbb"},
domProps:{"aaa":11}
}) `
複製代碼
頁面標籤看不到 aaa
能夠在 dom 屬性中找到 aaa
事件的拼接,內容不少,打算放在另外一篇文章詳細記錄
事件拼接還分爲兩種,原生事件和 自定義事件,只是拼接爲不一樣字符串而已,可是處理方法同樣
方法中涉及到各類 修飾符,哈哈,想知道到底爲何能寫出這麼方便的 api 呢哈哈
綁定按鍵,阻止默認事件,直接這麼寫就好了
@keyup.enter.prevent="xxx"
複製代碼
歡迎觀看下篇文章
就是直接拼接上 slot 這個屬性
` _c('test',[_c('span',{
attrs:{"slot":"name"},
slot:"name"
})]
) `
複製代碼
若是組件有slot,沒有 slot 這個屬性,那麼就不會拼接上slot,後面會直接給個默認名字 「default」
function 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 的函數看起來好像有一點複雜,可是其實就是紙老虎
不怕,先看一個實例
拼接成字符串,是這樣的
`
_c('div',{
scopedSlots:_u([{
key:"heder",
fn:function(arr){return _c('div')}
}])
})
`
複製代碼
這個函數遍歷的是 el.scopeSlots 這個數組,或許你不知道這個數組是什麼內容?
一樣給個例子,這裏有兩個 slot
通過 parse 解析以後成一個 ast,是這樣的
{
tag:"test",
scopedSlots:[{
slotScope: "arr"
slotTarget: ""a""
tag: "div"
},{
slotScope: "arr"
slotTarget: ""b""
tag: "div"
}]
}
複製代碼
沒錯,遍歷的就是上面對象裏面的 scopedSlots 數組,數組中的每一項都是一個單獨的 slot
而後會使用 genScopeSlot 去單獨處理一下,上面有放出源碼
處理完以後,造成一個新的數組,genScopeSlot 也沒什麼好說的
拼接分類型,須要判斷 slot 位置的標籤是否是 template
若是是template,那麼他的真實slot 是 template 的子節點,直接獲取他的子節點
若是不是template,那麼自己就是真實的slot
由於 template 是Vue 自帶的一個 模板節點,是不存在的
沒錯,這裏的 model,只是屬於 組件的 v-model
if (el.model) {
data += `model: {
value: $ { el.model.value },
callback: $ { el.model.callback },
expression: $ { el.model.expression },
}, `
}
複製代碼
官網說了這個是怎麼用的
一個組件上的 v-model 默認會利用名爲 value 的 prop 和名爲 input 的事件
也就是說,起始就是給組件傳了一個 value,綁定了一個事件 input
也沒有什麼好講的,記錄下組件的 v-model 是這麼拼接就行了
通過 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 的內容了
之後你就能夠去看別人用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')])
]) `
複製代碼
鑑於本人能力有限,不免會有疏漏錯誤的地方,請你們多多包涵,若是有任何描述不當的地方,歡迎後臺聯繫本人,領取紅包