詳解Vue如何手寫虛擬dom并進行渲染
虛擬dom如何渲染為真實dom
虛擬dom轉(zhuǎn)換為真實dom其實就是編寫一個渲染函數(shù),將虛擬dom逐個創(chuàng)建為真實的dom元素并將對應(yīng)事件添加到當(dāng)前創(chuàng)建的元素上,再掛載到頁面的指定元素下。
認識虛擬dom
虛擬dom其實就是用來描述真實dom的javascript對象。
來看下面例子,把一個真實dom用虛擬dom來進行描述:
真實dom
<div onclick="onAlert()"><span>點擊我</span><span></span></div> <script> function onAlert() { alert('點擊事件回調(diào)函數(shù)') } </script>
轉(zhuǎn)換為虛擬dom
const vnode = { tag: 'div', props: { onClick: () => alert('點擊事件回調(diào)函數(shù)') }, children: [ { tag: 'span', children: '點擊我' }, { tag: 'span', } ] }
渲染器實現(xiàn)
/** * @param {object} vnode 虛擬dom對象 * @param {HTMLElement} container 掛載虛擬dom的真實dom容器 */ function renderer(vnode, container) { const { tag, props, children } = vnode const el = document.createElement(tag) for(const key in props) { if(/^on/.test(key)) { // 轉(zhuǎn)換為合法的監(jiān)聽事件名稱 const eventNmae = key.substring(2).toLowerCase() // 在當(dāng)前創(chuàng)建的el元素上掛載監(jiān)聽事件 el.addEventListener(eventNmae, props[key]) } } if(typeof children === 'string') { // 創(chuàng)建一個文本節(jié)點添加到el元素下 el.appendChild(document.createTextNode(children)) } else if(Array.isArray(children)) { // 子節(jié)點為數(shù)組,遞歸調(diào)用renderer函數(shù) children.forEach(vnode => renderer(vnode, el)) } console.log(container) // 將元素掛載到容器上 container.appendChild(el) }
現(xiàn)在將上面轉(zhuǎn)換的虛擬dom傳入函數(shù)執(zhí)行看下效果
// 把虛擬dom渲染到id為app的元素下 renderer(vnode, document.getElementById('app'))
下圖可看到虛擬dom已經(jīng)成功渲染為真實dom,并且點擊事件也成功觸發(fā)了!
虛擬dom描述組件
以上講了如何使用虛擬dom(vnode
)描述真實dom,但還不夠!如果我們封裝了一個組件
,又該如何使用虛擬dom進行描述呢?
總的來說,組件就是一組dom元素的封裝,這組dom元素就是組件要渲染的內(nèi)容,比如前面例子的vnode
對象就可以認為是一個組件。
方法組件
const MyComponent = function () { return { tag: 'div', props: { onClick: () => alert('MyComponent點擊事件回調(diào)函數(shù)') }, children: [ { tag: 'span', children: 'MyComponent' }, { tag: 'span', } ] } }
對象組件
const MyComponent2 = { render() { return { tag: 'div', props: { onClick: () => alert('MyComponent2點擊事件回調(diào)函數(shù)') }, children: [ { tag: 'span', children: 'MyComponent2' }, { tag: 'span', } ] } } }
修改渲染器支持組件渲染
/** * @param {object} vnode 虛擬dom對象 * @param {HTMLElement} container 掛載虛擬dom的真實dom容器 */ function renderer(vnode, container) { const { tag } = vnode if(typeof tag === 'string') { mountElement(vnode, container) } else if(typeof tag === 'function') { mountComponent(tag(), container) } else if(typeof tag === 'object') { mountComponent(tag.render(), container) } } function mountElement(vnode, container) { const { tag, props, children } = vnode const el = document.createElement(tag) for(const key in props) { if(/^on/.test(key)) { // 轉(zhuǎn)換為合法的監(jiān)聽事件名稱 const eventNmae = key.substring(2).toLowerCase() // 在當(dāng)前創(chuàng)建的el元素上掛載監(jiān)聽事件 el.addEventListener(eventNmae, props[key]) } } if(typeof children === 'string') { // 創(chuàng)建一個文本節(jié)點添加到el元素下 el.appendChild(document.createTextNode(children)) } else if(Array.isArray(children)) { // 子節(jié)點為數(shù)組,遞歸調(diào)用renderer函數(shù) children.forEach(vnode => renderer(vnode, el)) } console.log(container) // 將元素掛載到容器上 container.appendChild(el) } function mountComponent(vnode, container) { // 遞歸調(diào)用renderer renderer(vnode, container) }
渲染組件
const vnode = { tag: 'div', children: [ { tag: 'span', props: { onClick: () => alert('span點擊事件回調(diào)函數(shù)') }, children: '我是span標簽' }, // 組件 { tag: MyComponent, }, // 組件 { tag: MyComponent2, } ] } // 把虛擬dom渲染到id為app的元素下 renderer(vnode, document.getElementById('app'))
下圖可看到,對應(yīng)的組件及事件都已經(jīng)掛載成功!
總結(jié)
最后,是不是覺得渲染器其實也沒有想象中那么難!其實這只是一個創(chuàng)建節(jié)點的渲染器,但其精髓在于更新節(jié)點。假設(shè)我們對虛擬dom做了一些小修改,渲染器需要精確找到vnode對象的變更點且只更新變更的內(nèi)容,而不是重新走一遍創(chuàng)建節(jié)點的流程。
以上就是詳解Vue如何手寫虛擬dom并進行渲染的詳細內(nèi)容,更多關(guān)于Vue虛擬dom的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
VUE前端實現(xiàn)token的無感刷新3種方案(refresh_token)
這篇文章主要給大家介紹了關(guān)于VUE前端實現(xiàn)token的無感刷新3種方案(refresh_token)的相關(guān)資料,為了提供更好的用戶體驗,我們可以通過實現(xiàn)Token的無感刷新機制來避免用戶在使用過程中的中斷,需要的朋友可以參考下2023-11-11詳解利用eventemitter2實現(xiàn)Vue組件通信
這篇文章主要介紹了詳解利用eventemitter2實現(xiàn)Vue組件通信,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11vue3父子同信的雙向數(shù)據(jù)的項目實現(xiàn)
我們知道的是,父傳子的通信,和子傳父的通信,那如何實現(xiàn)父子相互通信的呢,本文就來詳細的介紹一下,感興趣的可以了解一下2023-08-08vue.js+element-ui動態(tài)配置菜單的實例
今天小編就為大家分享一篇vue.js+element-ui動態(tài)配置菜單的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-09-09