一文詳解Vue中渲染器的簡單實現(xiàn)
一、渲染器
渲染器用于完成渲染操作,比如在瀏覽器平臺上渲染器可以將虛擬DOM轉(zhuǎn)換為真實DOM。
二、一個簡單例子理解Vue中渲染器的工作過程
const { effect, ref } = VueReactivity function renderer(domString, container) { container.innerHTML = domString } const count = ref(1) effect(() => { renderer(`<h1>${count.value}</h1>`, document.getElementById('app')) }) count.value++
我們通過@vue/reactivity
引用vue的響應(yīng)式API,使用其中的effect
(副作用函數(shù))和ref
(生成響應(yīng)式數(shù)據(jù))使得count
和renderer
建立聯(lián)系,這樣當 count
變化時,會調(diào)用renderer
處理渲染,將h1
標簽掛載到id為app
的元素上。這就是一個簡易的渲染器實現(xiàn)。
三、渲染器涉及的幾種操作: 掛載、更新、卸載
假設(shè)我們已經(jīng)有了一個渲染器renderer
:
const renderer = createRenderer()
有一個用于描述元素節(jié)點的數(shù)據(jù)vnode
:類似下面的結(jié)構(gòu)
// type為類型,children為子節(jié)點 const vnode = { type: 'h1', children: 'hello' }
可以看到這是一個內(nèi)部有hello文本的<h1>
節(jié)點
那么在渲染器實際工作過程中會有如下的幾種情況:
renderer.render(vnode, document.querySelector('#app'))
此時涉及到的操作是掛載,只需要將vnode變?yōu)镈OM元素放在app元素內(nèi)部即可
renderer.render(newVNode, document.querySelector('#app'))
由于已經(jīng)有舊的節(jié)點存在,所以不能簡單的直接掛載,需要對新舊節(jié)點進行對比,找到需要更新的部分再改變,此時的操作就是更新,為了處理更新,渲染器中需要有對應(yīng)的邏輯。
renderer.render(null, document.querySelector('#app'))
新的節(jié)點為null則意味這渲染器需要清空app
元素內(nèi)的內(nèi)容,此時涉及的操作是卸載
根據(jù)對以上三種情況的處理,可以將渲染器寫為:
function createRenderer() { function patch(n1, n2, container) { } function render(vnode, container) { if (vnode) { // 新 vnode 存在,將其與舊 vnode 一起傳遞給 patch 函數(shù)進行打補丁 patch(container._vnode, vnode, container) } else { if (container._vnode) { // 舊 vnode 存在,且新 vnode 不存在,說明是卸載(unmount)操作 // 只需要將 container 內(nèi)的 DOM 清空即可 container.innerHTML = '' } } // 把 vnode 存儲到 container._vnode 下,即后續(xù)渲染中的舊 vnode container._vnode = vnode } return { render } }
使用createRenderer重復(fù)上面三次渲染,分析過程:
// 首次渲染 renderer.render(vnode, document.querySelector('#app')) // 第二次渲染 renderer.render(newVNode, document.querySelector('#app')) //第三次渲染 renderer.render(null, document.querySelector('#app'))
- 首次渲染:
vnode
被渲染,并存在container._vnode
中; - 第二次渲染: 新舊
vnode
均存在,需要在patch
函數(shù)中進行更新; - 第三次渲染: 新的
vnode
為null
判斷舊節(jié)點container._vnode
是否存在,若舊的vnode
存在,則處理為卸載;
四、如何實現(xiàn)一個與平臺無關(guān)的渲染器:
一個通用的渲染器應(yīng)該是是與平臺無關(guān)的,即渲染器的渲染功能不能只在某一個平臺中有效。 為了實現(xiàn)這個目標,我們首先來實現(xiàn)一個基于瀏覽器平臺的渲染器,觀察在哪些步驟中使用到了和瀏覽器相關(guān)的API,之后可以考慮將這些API抽離,作為配置項,這樣就可以實現(xiàn)一個通用的渲染器。
1.實現(xiàn)一個依賴瀏覽器API的渲染器
以之前的的vnode
為例:
const vnode = { type: 'h1', children: 'hello' }
type
為標簽類型、children
為子元素。
在以上實現(xiàn)的基礎(chǔ)上我們首先完善patch
函數(shù),用于處理節(jié)點的掛載和更新情況
patch(container._vnode, vnode, container)
通過之前處理的代碼,可以看到,patch
函數(shù)會接受三個參數(shù),分別是 舊的節(jié)點、新的節(jié)點、以及容器, 我們在其中對舊節(jié)點參數(shù)進行判斷,從而處理不同的操作:目前只分析掛載的情況:
function patch(n1, n2, container) { if (!n1) { mountElement(n2, container) } else { // } }
如上代碼所示: 當n1
即舊節(jié)點不存在時證明是掛載操作,則直接調(diào)用mountElement
對新的節(jié)點進行掛載處理
function mountElement(vnode, container) { const el = createElement(vnode.type) if (typeof vnode.children === 'string') { el.textContent = vnode.children } container.appendChild(el) }
在mountElement
中我們對掛載的邏輯進行了簡單的處理:
1. 使用createElement
根據(jù)vnode中的type
的值去創(chuàng)建對應(yīng)的DOM類型: 如vnode.type='h1'
則el為<h1></h1>
2. 對vnode.children進行判斷: 如果vnode.children
為字符串類型則證明子節(jié)點是一個文本節(jié)點,直接使用元素的textContent
屬性設(shè)置元素。
3. 調(diào)用appendChild將處理好的元素添加到目標容器中。由此掛載完成。
2.實現(xiàn)一個通用的渲染器
以上實現(xiàn)的渲染器對于瀏覽器的API是有依賴的,其中的appendChild
、createElement
、textContent
都是需要瀏覽器環(huán)境才能執(zhí)行的API,如果要讓渲染器能夠通用,就需要去除對這些API的依賴。
解決辦法:將這些API作為配置項傳入渲染器創(chuàng)建函數(shù),這樣我們可以自己定義createRenderer
使用哪些API去執(zhí)行渲染操作,從而使得渲染器的工作不再依賴于某一平臺。
const renderer2 = createRenderer({ //創(chuàng)建元素 createElement(tag) { return { tag } }, //設(shè)置元素文本內(nèi)容 setElementText(el, text) { console.log(`設(shè)置 ${JSON.stringify(el)} 的文本內(nèi)容:${text}`) el.text = text }, //將元素插入目標節(jié)點 insert(el, parent, anchor = null) { parent.children = el } })
我們將創(chuàng)建元素的邏輯和API放入createElement
中,將設(shè)置文本內(nèi)容的API放入setElementText
中, 將元素掛載到容器的API過程放入insert
中。 再使用傳入的配置去調(diào)用mountElement
function mountElement(vnode, container) { const el = createElement(vnode.type) if (typeof vnode.children === 'string') { setElementText(el, vnode.children) } insert(el, container) }
這樣我們可以通過傳入的createRenderer
函數(shù)的options去
配置,不同平臺中使用什么樣的方法去執(zhí)行元素的創(chuàng)建、元素內(nèi)容的處理以及元素掛載等操作。
由此整個渲染函數(shù)如下:
function createRenderer(options) { const { createElement, insert, setElementText } = options function mountElement(vnode, container) { const el = createElement(vnode.type) if (typeof vnode.children === 'string') { setElementText(el, vnode.children) } insert(el, container) } function patch(n1, n2, container) { if (!n1) { mountElement(n2, container) } else { // } } function render(vnode, container) { if (vnode) { // 新 vnode 存在,將其與舊 vnode 一起傳遞給 patch 函數(shù)進行打補丁 patch(container._vnode, vnode, container) } else { if (container._vnode) { // 舊 vnode 存在,且新 vnode 不存在,說明是卸載(unmount)操作 // 只需要將 container 內(nèi)的 DOM 清空即可 container.innerHTML = '' } } // 把 vnode 存儲到 container._vnode 下,即后續(xù)渲染中的舊 vnode container._vnode = vnode } return { render } }
由此一個簡單的渲染器已經(jīng)實現(xiàn),但在實際情況下元素的掛載和更新還有更多的細節(jié)現(xiàn)需要處理,如元素上的屬性、class、事件如何被正確的掛載,如何提升渲染器更新的效率(diff算法)等,這些將在后續(xù)文章中進行討論。
以上就是一文詳解Vue中渲染器的簡單實現(xiàn)的詳細內(nèi)容,更多關(guān)于Vue實現(xiàn)渲染器的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue中使用axios post上傳頭像/圖片并實時顯示到頁面的方法
今天小編就為大家分享一篇vue中使用axios post上傳頭像/圖片并實時顯示到頁面的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-09-09vue框架和react框架的區(qū)別以及各自的應(yīng)用場景使用
這篇文章主要介紹了vue框架和react框架的區(qū)別以及各自的應(yīng)用場景使用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10vue2實現(xiàn)搜索結(jié)果中的搜索關(guān)鍵字高亮的代碼
這篇文章主要介紹了vue2實現(xiàn)搜索結(jié)果中的搜索關(guān)鍵字高亮的代碼,需要的朋友可以參考下2018-08-08教你使用vue-autofit 一行代碼搞定自適應(yīng)可視化大屏
這篇文章主要為大家介紹了使用vue-autofit 一行代碼搞定自適應(yīng)可視化大屏教程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-05-05element-ui使用導航欄跳轉(zhuǎn)路由的用法詳解
今天小編就為大家分享一篇element-ui使用導航欄跳轉(zhuǎn)路由的用法詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08