Vue手寫實現(xiàn)組件初渲染
前言
在Vue
進(jìn)行文本編譯之后,會得到代碼字符串生成的render
函數(shù)。本文會基于render
函數(shù)介紹以下內(nèi)容:
- 執(zhí)行
render
函數(shù)生成虛擬節(jié)點 - 通過
vm._update
方法,將虛擬節(jié)點渲染為真實DOM
在vm.$mount
方法中,文本編譯完成后,要進(jìn)行組件的掛載,代碼如下:
Vue.prototype.$mount = function (el) { // text compile code .... mountComponent(vm); }; // src/lifecycle.js export function mountComponent (vm) { vm._update(vm._render()); }
下面詳細(xì)介紹vm._render()
和vm._update()
中到底做了什么
生成虛擬節(jié)點
原生DOM
節(jié)點擁有大量的屬性和方法,操作DOM
比較耗費性能。在Vue
中通過一個對象來描述DOM
中的節(jié)點,這個對象就是虛擬節(jié)點,Vue
組件樹構(gòu)建的整個虛擬節(jié)點樹就是虛擬DOM
。
這是一段html
<div id="app"> <span>hello world {{name}}</span> </div> <script> new Vue({ el: '#app', data () { return { name: 'zs' } } }) </script>
其對應(yīng)的虛擬節(jié)點如下:
const vNode = { tag: 'div', props: { id: 'app' }, key: undefined, children: [ { tag: 'span', props: {}, key: undefined, children: undefined, text: 'helloworldzs' } ], text: undefined }
在Vue.prototype._render
函數(shù)中,通過執(zhí)行文本編譯后生成的render
方法,會得到虛擬節(jié)點:
// src/vdom/index.js Vue.prototype._render = function () { const vm = this; // 執(zhí)行選項中的render方法,指定this為Vue實例 const { render } = vm.$options; return render.call(vm); };
而render
函數(shù)中用到了_c
,_v
,_s
這些方法,需要在Vue.prototype
上添加這些方法,在render
函數(shù)內(nèi)就可以通過實例調(diào)用它們:
// 創(chuàng)建虛擬節(jié)點 function vNode (tag, props, key, children, text) { return { tag, props, key, children, text }; } // 創(chuàng)建虛擬元素節(jié)點 function createVElement (tag, props = {}, ...children) { const { key } = props; delete props.key; return vNode(tag, props, key, children); } // 創(chuàng)建虛擬文本節(jié)點 function createTextVNode (text) { return vNode(undefined, undefined, undefined, undefined, text); } // 將實例中data里的值轉(zhuǎn)換為字符串 function stringify (value) { if (value == null) { return ''; } else if (typeof value === 'object') { return JSON.stringify(value); } else { return value; } } export function renderMixin (Vue) { Vue.prototype._c = createVElement; Vue.prototype._v = createTextVNode; Vue.prototype._s = stringify; // some code ... }
_render
函數(shù)最終會遞歸的調(diào)用這些函數(shù)來得到虛擬節(jié)點,并將其返回:
const vNode = vm.createVElement('div', { id: 'app' }, vm.createVElement('span', undefined, vm.createTextVNode('hello') + vm.createTextVNode('world') + vm.stringify(vm.name) ) )
在生成虛擬節(jié)點的過程中,會從組件實例vm
中取值,從而觸發(fā)對應(yīng)屬性的get/set
方法。
將虛擬節(jié)點處理為真實節(jié)點
在通過Vue.prototype._render
函數(shù)生成虛擬節(jié)點后,在Vue.prototype._update
方法中會利用虛擬節(jié)點,替換當(dāng)前頁面上渲染的元素app
。
其代碼如下:
// src/lifecycle.js export function lifecycleMixin (Vue) { Vue.prototype._update = function (vNode) { const vm = this; patch(vm.$el, vNode); }; }
在patch
方法中,會通過虛擬節(jié)點創(chuàng)建真實節(jié)點,并將真實節(jié)點插入頁面中:
// src/vdom/patch.js export function patch (oldVNode, vNode) { // 將虛擬節(jié)點創(chuàng)建為真實節(jié)點,并插入到dom中 const el = createElement(vNode); // 獲取到老節(jié)點的父節(jié)點 const parentNode = oldVNode.parentNode; // 將新節(jié)點插入到老節(jié)點之后 parentNode.insertBefore(el, oldVNode.nextSibling); // 刪除老節(jié)點 parentNode.removeChild(oldVNode); }
createElement
中是用虛擬節(jié)點生成真實節(jié)點的邏輯:
- 通過
document.createElement
來創(chuàng)建元素節(jié)點 - 元素節(jié)點通過
updateProperties
方法來設(shè)置它的屬性 - 通過
document.createTextNode
來創(chuàng)建文本節(jié)點
function createElement (vNode) { if (typeof vNode.tag === 'string') { vNode.el = document.createElement(vNode.tag); updateProperties(vNode); for (let i = 0; i < vNode.children.length; i++) { const child = vNode.children[i]; vNode.el.appendChild(createElement(child)); } } else { vNode.el = document.createTextNode(vNode.text); } return vNode.el; }
createElement
會生成的真實DOM
元素el
并返回,內(nèi)部會對子虛擬節(jié)點再次調(diào)用createElement
來繼續(xù)生成真實元素,然后將生成的真實元素通過appendChild
方法插入到父節(jié)點中。
執(zhí)行createElement
最后得到的el
是將所有子節(jié)點都插入到內(nèi)部的元素,但其實el
此時還是脫離真實DOM
存在的,最后將它插入到真實DOM
中便完成了整個真實節(jié)點的渲染。
下面是其執(zhí)行邏輯示意圖:
總結(jié)
Vue的組件掛載vm.$mount(el)過程如下:
- 將
template
編譯為render
函數(shù) - 使用
render
函數(shù)生成虛擬節(jié)點,函數(shù)中需要的變量和方法會去vm
的自身和原型鏈中查找 - 將虛擬節(jié)點創(chuàng)建為真實節(jié)點,并遞歸的插入到頁面中
- 使用真實節(jié)點替換之前老的節(jié)點
到目前為止,我們已經(jīng)實現(xiàn)了Vue
組件初渲染的整個過程,下面用一張圖來總結(jié)一下:
到此這篇關(guān)于Vue手寫實現(xiàn)組件初渲染的文章就介紹到這了,更多相關(guān)Vue組件初渲染內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
源碼地址: 傳送門
相關(guān)文章
詳解vue 動態(tài)加載并注冊組件且通過 render動態(tài)創(chuàng)建該組件
這篇文章主要介紹了vue 動態(tài)加載并注冊組件且通過 render動態(tài)創(chuàng)建該組件,本文通過實例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2019-05-05vue?contextmenujs鼠標(biāo)右鍵菜單高度不夠顯示不全的問題及解決方法
這篇文章主要介紹了使用vue-contextmenujs鼠標(biāo)右鍵菜單時,當(dāng)高度不夠時顯示不全的問題,大家需要注意本文給提供的解決方案雖然能夠解決現(xiàn)有問題,但是如果組件升級了,想要使用最新升級后的組件,還要再次修改代碼,需要的朋友可以參考下2022-07-07vue.js與element-ui實現(xiàn)菜單樹形結(jié)構(gòu)的解決方法
本文通過實例給大家介紹了vue.js與element-ui實現(xiàn)菜單樹形結(jié)構(gòu),非常不錯,具有參考借鑒價值,需要的朋友可以參考下2018-04-04Vue表格首列相同數(shù)據(jù)合并實現(xiàn)方法
這篇文章主要介紹了Vue實現(xiàn)表格首列相同數(shù)據(jù)合并的方法,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-04-04vue.js中關(guān)于點擊事件方法的使用(click)
這篇文章主要介紹了vue.js中關(guān)于點擊事件方法的使用(click),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08