Vue3渲染器與編譯器深入淺析
渲染器
相關(guān)概念
渲染器是Vue框架性能的核心,Vue3的渲染器不僅包含傳統(tǒng)的Diff算法,還獨(dú)創(chuàng)了快捷的路徑更新方式,可以充分利用渲染器提供的信息,大大提升更新和渲染的性能;
- Vue中的
h函數(shù)返回的就是一個對象,其作用是讓編寫的虛擬DOM變得更加輕松,換句話說h函數(shù)就是一個輔助創(chuàng)建虛擬DOM的工具函數(shù)而已; 虛擬DOM就是用來描述真實(shí)DOM的普通JS對象,渲染器會將該對象渲染成真實(shí)DOM元素,當(dāng)然虛擬DOM也可以描述組件信息,而組件又可以理解成為一組DOM元素的封裝- 組件的
渲染函數(shù):一個組件要渲染的內(nèi)容是通過渲染函數(shù)來描述的,也就是Vue中的render函數(shù),Vue會根據(jù)組件的render函數(shù)的返回值拿到虛擬DOM,然后將組件的內(nèi)容渲染出來; - 渲染器在渲染組件時,需要先通過執(zhí)行組件的渲染函數(shù)拿到返回值即組件要渲染的內(nèi)容,可以稱為為
subTree,最后再遞歸調(diào)用渲染器將subTree渲染出來即可
組件可以通過函數(shù)或JS對象來實(shí)現(xiàn)
- 通過函數(shù)實(shí)現(xiàn)時:該函數(shù)返回一個描述DOM節(jié)點(diǎn)的虛擬DOM即可,此時渲染器的Tag屬性值就是該組件函數(shù)了,對應(yīng)渲染器中也需要進(jìn)行判斷Tag為函數(shù)時進(jìn)行特殊處理即可(即通過執(zhí)行該函數(shù)拿到對應(yīng)描述普通節(jié)點(diǎn)的虛擬DOM然后執(zhí)行渲染器renderer即可)
- 通過JS對象實(shí)現(xiàn)時:其renderer返回值就是一個描述節(jié)點(diǎn)的虛擬DOM,因此可以直接執(zhí)renderer函數(shù)即可得到虛擬DOM
// MyComponent 是一個對象
const MyComponent = {
render() {
return {
tag: "div",
props: {
onClick: () => alert("hello"),
},
children: "click me",
};
},
};
function mountComponent(vnode, container) {
// 調(diào)用組件函數(shù),獲取組件要渲染的內(nèi)容(虛擬 DOM)
const subtree = vnode.tag();
// 遞歸地調(diào)用 renderer 渲染 subtree
renderer(subtree, container);
}
// MyComponent 是一個函數(shù)
function renderer(vnode, container) {
if (typeof vnode.tag === "string") {
// 說明 vnode 描述的是標(biāo)簽元素
// mountElement內(nèi)部實(shí)現(xiàn)與下文的「實(shí)現(xiàn)renderer渲染器邏輯一致」
mountElement(vnode, container);
} else if (typeof vnode.tag === "function") {
// 說明 vnode 描述的是組件
mountComponent(vnode, container);
}
}
function mountComponent(vnode, container) {
// 調(diào)用組件函數(shù),獲取組件要渲染的內(nèi)容(虛擬 DOM)
const subtree = vnode.tag();
// 遞歸地調(diào)用 renderer 渲染 subtree
renderer(subtree, container);
}

render與renderer
- renderer代表
渲染器,其作用是將虛擬DOM渲染為特定平臺上的真實(shí)DOM,如在瀏覽器平臺上,渲染器會將虛擬DOM渲染為真實(shí)DOM - render表示
渲染,是執(zhí)行的動作 - 渲染器不僅用來
渲染,還可以用來執(zhí)行其他操作,如激活已有DOM、SSR等;是更寬泛的概念,renderer包含render; - 渲染器除了普通的
創(chuàng)建節(jié)點(diǎn)功能外,還需要實(shí)現(xiàn)精確的定點(diǎn)更新對應(yīng)變化的虛擬DOM到界面上,其內(nèi)部原理簡單,歸根結(jié)底就是通過熟悉的DOM操作API來完成渲染工作
實(shí)現(xiàn)renderer渲染器
function renderer(vnode, container) {
// 使用 vnode.tag 作為標(biāo)簽名稱創(chuàng)建對應(yīng)的 DOM 元素
const el = document.createElement(vnode.tag);
// 遍歷 vnode.props,將屬性、事件添加到 DOM 元素
for (const key in vnode.props) {
if (/^on/.test(key)) {
// 如果 key 以 on 開頭,說明它是事件
el.addEventListener(
key.substr(2).toLowerCase(), // 事件名稱 onClick ---> click
vnode.props[key] // 事件處理函數(shù)
);
}
}
// 處理 children
if (typeof vnode.children === "string") {
// 如果 children 是字符串,說明它是元素的文本子節(jié)點(diǎn)
el.appendChild(document.createTextNode(vnode.children));
} else if (Array.isArray(vnode.children)) {
// 遞歸地調(diào)用 renderer 函數(shù)渲染子節(jié)點(diǎn),使用當(dāng)前元素 el 作為掛載點(diǎn)
vnode.children.forEach((child) => renderer(child, el));
}
// 將元素添加到掛載點(diǎn)下
container.appendChild(el);
}
- 創(chuàng)建元素:對應(yīng)虛擬DOM中的Tag屬性
- 為創(chuàng)建的元素添加屬性和事件:對應(yīng)虛擬DOM中的Props屬性
- 添加子元素:對應(yīng)虛擬DOM中的children屬性
- 這里的children可以是數(shù)組或字符串,數(shù)組時需要以剛才新建的DOM節(jié)點(diǎn)為父節(jié)點(diǎn)進(jìn)行回調(diào)渲染器renderer進(jìn)行遞歸渲染,當(dāng)是普通字符串時表明子節(jié)點(diǎn)是一個普通文本,直接調(diào)用createTextNode API進(jìn)行創(chuàng)建后添加到新創(chuàng)建的元素內(nèi)部即可
具體代碼實(shí)現(xiàn)邏輯
角色解析
- 控制著Vue生命周期里試圖的掛載、更新和渲染
具體作用
- 創(chuàng)建VNode(h函數(shù))
- 掛載(mount)/渲染(render)/更新(patch)
- 渲染的核心diff算法
Vue3特殊內(nèi)置組件解析
<template>
// Portal 把里面的內(nèi)容掛載到 #app
<Portal target="#app">
<div class="overlay"></div>
</Portal>
</template>
- Fragment
- 只將該VNode的子節(jié)點(diǎn)渲染到頁面上
- 相當(dāng)于Vue2中的template標(biāo)簽
- Portal
- 允許將其中的內(nèi)容渲染到任何地方
渲染器階段分析
mount掛載階段
mount就是將VNode掛載到真實(shí)DOM上
patch階段
patch就是使用新的VNode和舊的VNode進(jìn)行對比,用最少的資源實(shí)現(xiàn)DOM更新,也叫「打補(bǔ)丁」
簡單實(shí)現(xiàn)
渲染器接受兩個參數(shù),要被渲染的VNode和掛載點(diǎn)(承載內(nèi)容的container)
function createRenderer() {
function render(vnode, container) {
if (vnode) {
// 存在舊節(jié)點(diǎn) 需要進(jìn)行patch補(bǔ)丁更新
patch(container._vnode, vnode, container);
} else {
if (container._vnode) {
// 舊 vnode 存在,且新 vnode 不存在,此時渲染的是上一步中的節(jié)點(diǎn)值,說明是卸載(unmount)操作
container.innerHTML = "";
}
}
// 把 vnode 存儲到 container._vnode 下,即后續(xù)渲染中的舊 vnode
container._vnode = vnode;
}
return {
render,
};
}
編譯器
編譯器在模板編譯時可以識別哪些是靜態(tài)屬性,哪些是動態(tài)屬性,從而在編譯生成代碼時可以將這些信息附加到編譯后代碼中,這樣可以避免渲染器花費(fèi)力氣去尋找變更點(diǎn)
相關(guān)概念
編譯器和渲染器一樣,不同之處在于編譯器是將模板編譯成渲染函數(shù),而模板就是一個普通的字符串,編譯器會分析該字符串并生成一個功能與之相同的渲染函數(shù);
模板在Vue中體現(xiàn)在.vue文件中的<template>標(biāo)簽的內(nèi)容,本身.vue文件就是一個組件,編譯器會將模板內(nèi)容編譯成渲染函數(shù)并添加到<script>標(biāo)簽的組件對象上;
<template>
<div @click="login">
登錄
</div>
</template>
<script>
export default {
data() {
},
methods: {
login: () => { }
}
}
</script>
//編譯后
export default {
data() {},
methods: {
login: () => { }
},
render() {
return h('div', { onClick: login }, '登錄')
}
}
總結(jié)
通過將渲染器和編譯器相配合,從而達(dá)到進(jìn)一步提升性能的目的
- 虛擬DOM和模板都可以描述UI,虛擬DOM比模板更加靈活,模板比虛擬DOM更加直觀
組件的實(shí)現(xiàn)需要依賴于渲染器,模板的編譯需要依賴于編譯器,而且編譯后的代碼是根據(jù)渲染器和虛擬DOM的設(shè)計(jì)決定的,因此Vue中各個模塊之間是相互關(guān)聯(lián)、相互制約的;
模板工作原理:無論是模板還是直接手寫渲染函數(shù),對于一個組件來說,其渲染內(nèi)容最終都是通過渲染函數(shù)產(chǎn)生的,然后渲染器再將渲染函數(shù)返回 的虛擬DOM渲染為真實(shí)DOM
以上就是Vue3渲染器與編譯器深入淺析的詳細(xì)內(nèi)容,更多關(guān)于Vue3渲染器編譯器的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
如何解決Vue3組合式API模式下動態(tài)組件不渲染問題
這篇文章主要介紹了如何解決Vue3組合式API模式下動態(tài)組件不渲染問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教<BR>2024-03-03
el-tree設(shè)置選中高亮/焦點(diǎn)高亮、選中節(jié)點(diǎn)加深背景及更改字體顏色等的方法
el-tree默認(rèn)有較淺的背景色,這里業(yè)務(wù)需要,選中節(jié)點(diǎn)的字體高亮,更改顏色,下面這篇文章主要給大家介紹了關(guān)于el-tree選中高亮/焦點(diǎn)高亮、選中節(jié)點(diǎn)加深背景及更改字體顏色等的設(shè)置方法,需要的朋友可以參考下2022-12-12
vue-cli項(xiàng)目使用vue-picture-preview圖片預(yù)覽組件方式
這篇文章主要介紹了vue-cli項(xiàng)目使用vue-picture-preview圖片預(yù)覽組件方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09
vue+elementUI實(shí)現(xiàn)動態(tài)面包屑
這篇文章主要為大家詳細(xì)介紹了vue+elementUI實(shí)現(xiàn)動態(tài)面包屑,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-04-04
vue+element下日期組件momentjs轉(zhuǎn)換賦值問題解決
這篇文章主要介紹了vue+element下日期組件momentjs轉(zhuǎn)換賦值問題,記錄下使用momentjs轉(zhuǎn)換日期字符串賦值給element的日期組件報錯問題,需要的朋友可以參考下2024-02-02
深入探討Vue計(jì)算屬性與監(jiān)聽器的區(qū)別和用途
在Vue的開發(fā)中,計(jì)算屬性(Computed Properties)和監(jiān)聽器(Watchers)是兩種非常重要的概念,它們都用于響應(yīng)式地處理數(shù)據(jù)變化,本文將帶你深入了解計(jì)算屬性和監(jiān)聽器的區(qū)別,以及在何時使用它們,感興趣的朋友可以參考下2023-09-09

