一文詳解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的響應式API,使用其中的effect(副作用函數(shù))和ref(生成響應式數(shù)據(jù))使得count和renderer建立聯(lián)系,這樣當 count變化時,會調(diào)用renderer處理渲染,將h1標簽掛載到id為app的元素上。這就是一個簡易的渲染器實現(xiàn)。
三、渲染器涉及的幾種操作: 掛載、更新、卸載
假設我們已經(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é)點進行對比,找到需要更新的部分再改變,此時的操作就是更新,為了處理更新,渲染器中需要有對應的邏輯。
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重復上面三次渲染,分析過程:
// 首次渲染
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)的渲染器:
一個通用的渲染器應該是是與平臺無關(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)的基礎上我們首先完善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)建對應的DOM類型: 如vnode.type='h1'則el為<h1></h1>
2. 對vnode.children進行判斷: 如果vnode.children為字符串類型則證明子節(jié)點是一個文本節(jié)點,直接使用元素的textContent屬性設置元素。
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 }
},
//設置元素文本內(nèi)容
setElementText(el, text) {
console.log(`設置 ${JSON.stringify(el)} 的文本內(nèi)容:${text}`)
el.text = text
},
//將元素插入目標節(jié)點
insert(el, parent, anchor = null) {
parent.children = el
}
})
我們將創(chuàng)建元素的邏輯和API放入createElement中,將設置文本內(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)文章
Vue3使用Element?Plus實現(xiàn)列表界面的方法步驟
寫后臺管理的時候會有很多列表以及相應的條件查詢,下面這篇文章主要給大家介紹了關(guān)于Vue3使用Element?Plus實現(xiàn)列表界面的方法步驟,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2023-04-04
Vue后臺管理系統(tǒng)權(quán)限控制與動態(tài)路由的實現(xiàn)
本文主要介紹了Vue后臺管理系統(tǒng)權(quán)限控制與動態(tài)路由的實現(xiàn),可以根據(jù)用戶的角色靈活控制頁面訪問權(quán)限,提高系統(tǒng)的安全性和用戶體驗,感興趣的可以了解一下2025-04-04
解決VUE3 keep-alive頁面切換報錯parentComponent.ctx.deactivate
這篇文章主要介紹了解決VUE3 keep-alive頁面切換報錯parentComponent.ctx.deactivate is not a function的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-10-10
手把手教你如何使用Vue+Django實現(xiàn)RBAC權(quán)限管理
在開發(fā)一個復雜的Web應用時,權(quán)限管理是繞不開的核心環(huán)節(jié),本文將和大家一起探索并實現(xiàn)一個基于角色權(quán)限管理系統(tǒng),感興趣的小伙伴可以跟隨小編一起學習一下2026-03-03
Vue3?接入?i18n?實現(xiàn)國際化多語言案例分析
在?Vue.js?3?中實現(xiàn)網(wǎng)頁的國際化多語言,最常用的包是?vue-i18n,通常我們會與?vue-i18n-routing?一起使用,這篇文章主要介紹了Vue3?如何接入?i18n?實現(xiàn)國際化多語言,需要的朋友可以參考下2024-07-07
Vuerouter的beforeEach與afterEach鉤子函數(shù)的區(qū)別
本文詳細的介紹了Vuerouter的beforeEach與afterEach鉤子函數(shù)的區(qū)別和使用,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12
vue通過tailwindcss實現(xiàn)class動態(tài)綁定
這篇文章主要介紹了vue通過tailwindcss實現(xiàn)class動態(tài)綁定,文中給大家介紹了一些常用類名語法記錄,對vue動態(tài)綁定class相關(guān)知識感興趣的朋友一起看看吧2023-07-07

