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

