當template內部的結構代碼在編譯的時候發生了什么?
比如我們下面的代碼
1 <template> 2 <div> 3 <h1>123456789</h1> 4 </div> 5 </template>
我們平常寫template里面所使用的模板是HTML語法組件的頁面,其實在vue中都會被編譯為render函數,因為vue中采用的是虛擬dom進行頁面組件,這樣的優點是優化頁面的加載重繪性能
render函數的基本使用
我們在views文件夾中新建一個Render.vue組件,注意如果要使用render函數編譯模板,一定不要有<template></template>,否則就回加載template中的內容
render.vue
1 <script> 2 export default { 3 render(createElement){ 4 return createElement('h1',{},123456789) 5 } 6 } 7 </script> 8 9 <style lang="scss" scoped> 10 </style>
App.vue
1 <template> 2 <div> 3 <render></render> 4 </div> 5 </template> 6 <script> 7 import render from "./views/render.vue" 8 export default { 9 components:{ 10 render 11 } 12 } 13 </script> 14 <style lang="less" scoped> 15 </style>
我們設置一個小案例,通過render函數動態的修改當前組件的節點渲染
1 <script> 2 export default { 3 props:{ 4 tag:{ 5 type:String, 6 required:true, 7 }, 8 data:{ 9 type:String 10 } 11 }, 12 render(createElement){ 13 return createElement(this.tag,{},this.data) 14 } 15 } 16 </script> 17 18 <style lang="scss" scoped> 19 </style>
props有兩個值,第一個值為tag,指的是傳入的節點名稱,第二個值data,就是要渲染的節點內容
App.vue
1 <template> 2 <div> 3 <render :tag="'p'" :data="'123456789'"></render> 4 </div> 5 </template>
此時要注意,如果沒有設置的元素節點,render函數也會加載
1 <render :tag="'aaaaa'" :data="'123456789'"></render>
這樣做有一個好處,就是如果我們的節點是一個ui元素名稱,或者是自定義組件,都會被識別
比如我們引入的是element-ui
1 <render :tag="'el-button'" :data="'我是button'"></render>
也可以設置組件內容
1 <script> 2 import son from "./son.vue" 3 export default { 4 props:{ 5 tag:{ 6 type:String, 7 required:true, 8 }, 9 data:{ 10 type:String 11 } 12 }, 13 render(createElement){ 14 return createElement(son,{},this.data) 15 } 16 } 17 </script> 18 <style lang="scss" scoped> 19 </style>
createdElement函數一共有三個參數,第一個參數我們已經知道如何使用,第二個參數其實就是對當前的節點(組件)的屬性描述
1 <script> 2 export default { 3 props:{ 4 tag:{ 5 type:String, 6 required:true, 7 }, 8 data:{ 9 type:String 10 } 11 }, 12 render(createElement){ 13 return createElement(this.tag,{ 14 class:'color_red' 15 },this.data) 16 } 17 } 18 </script> 19 <style scoped> 20 .color_red{ 21 color: red; 22 } 23 </style>
我們設置class顏色為red
還可以用domProps設置
1 render(createElement){ 2 return createElement(this.tag,{ 3 // class:'color_red' 4 //Dom的prototype 5 domProps:{ 6 className:'color-red', 7 innerHTML:'123456789' 8 } 9 },this.data) 10 }
最重要的是第三個參數,第三個參數如果不是數組,則表示渲染內容,否則,如果設置了數組,內部必須是createElement函數,代表的是當前的元素再進行嵌套
1 render(createElement){ 2 return createElement(this.tag,{ 3 // class:'color_red' 4 //Dom的prototype 5 domProps:{ 6 className:'color-red', 7 8 } 9 },[createElement('p', [createElement('span', '我是p元素內部的span元素')]), createElement('p', '我是p元素')]) 10 }
createElement方法的核心其實就是第三個參數,因為這個參數最大的魔力就是能夠嵌套,由於之前能夠通過第二個參數設置當前元素的相關屬性,所以如果一旦第三個參數實現了嵌套元素的功能,此時就可以實現通過js設置HTML模板
render函數還有一個最大的功能就是解析模板
此時我們的render函數,並沒有通過字符串設置模板內容,而是直接設置了對應的元素
1 render(){ 2 return ( 3 <div> 4 <h2>四大名著</h2> 5 <ul> 6 <li>西游記</li> 7 <li>水滸傳</li> 8 <li>三國演義</li> 9 <li>紅樓夢</li> 10 </ul> 11 </div> 12 ) 13 }
頁面中進行了識別和解析
createElement源碼解析
我們先創建一個類似於createElement的結構
1 var vDom=createElement('div',{class:'container'},[ 2 createElement('p',{class:'item',style:'color:red'},'我是p節點1'), 3 createElement('p',{class:'item',style:'background:blue'},'我是p節點2'), 4 createElement('p',{class:'item'},'我是p節點3'), 5 createElement('input',{value:'我是value'},'我是input'), 6 ])
創建Element構造函數
1 function Element(type,props,children){ 2 this.type=type; 3 this.props=props; 4 this.children=children; 5 6 } 7 function createElement(type,props,children){ 8 return new Element(type,props,children) 9 }
創建節點和屬性
1 function render(obj){ 2 //創建節點 3 let el=document.createElement(obj.type); 4 //給相關的節點設置對應的屬性 5 for(let key in obj.props){ 6 el.setAttribute(key,obj.props[key]) 7 }; 8 if(Array.isArray(obj.children)){ 9 //遍歷創建子元素 10 obj.children.forEach((child)=>{ 11 // console.log(child) 12 //遞歸操作,如果當前的child不是文本節點,就繼續進行遞歸操作,否則渲染文本節點 13 child=child instanceof Element?render(child):document.createTextNode(child); 14 //節點上樹 15 el.appendChild(child) 16 }) 17 //當前obj.children如果不是數組而是字符串,就當做文本進行渲染 18 }else if (typeof obj.children==='string'){ 19 el.appendChild(document.createTextNode(obj.children)) 20 } 21 return el 22 }
渲染頁面
1 function renderDom(el,target){ 2 target.appendChild(el) 3 }
調用
1 renderDom(render(vDom),document.getElementById("app"))