Vue3渲染器與編譯器深入淺析
渲染器
相關(guān)概念
渲染器是Vue框架性能的核心,Vue3的渲染器不僅包含傳統(tǒng)的Diff算法,還獨(dú)創(chuàng)了快捷的路徑更新方式,可以充分利用渲染器提供的信息,大大提升更新和渲染的性能;
- Vue中的
h函數(shù)返回的就是一個(gè)對(duì)象,其作用是讓編寫(xiě)的虛擬DOM變得更加輕松,換句話說(shuō)h函數(shù)就是一個(gè)輔助創(chuàng)建虛擬DOM的工具函數(shù)而已; 虛擬DOM就是用來(lái)描述真實(shí)DOM的普通JS對(duì)象,渲染器會(huì)將該對(duì)象渲染成真實(shí)DOM元素,當(dāng)然虛擬DOM也可以描述組件信息,而組件又可以理解成為一組DOM元素的封裝- 組件的
渲染函數(shù):一個(gè)組件要渲染的內(nèi)容是通過(guò)渲染函數(shù)來(lái)描述的,也就是Vue中的render函數(shù),Vue會(huì)根據(jù)組件的render函數(shù)的返回值拿到虛擬DOM,然后將組件的內(nèi)容渲染出來(lái); - 渲染器在渲染組件時(shí),需要先通過(guò)執(zhí)行組件的渲染函數(shù)拿到返回值即組件要渲染的內(nèi)容,可以稱為為
subTree,最后再遞歸調(diào)用渲染器將subTree渲染出來(lái)即可
組件可以通過(guò)函數(shù)或JS對(duì)象來(lái)實(shí)現(xiàn)
- 通過(guò)函數(shù)實(shí)現(xiàn)時(shí):該函數(shù)返回一個(gè)描述DOM節(jié)點(diǎn)的虛擬DOM即可,此時(shí)渲染器的Tag屬性值就是該組件函數(shù)了,對(duì)應(yīng)渲染器中也需要進(jìn)行判斷Tag為函數(shù)時(shí)進(jìn)行特殊處理即可(即通過(guò)執(zhí)行該函數(shù)拿到對(duì)應(yīng)描述普通節(jié)點(diǎn)的虛擬DOM然后執(zhí)行渲染器renderer即可)
- 通過(guò)JS對(duì)象實(shí)現(xiàn)時(shí):其renderer返回值就是一個(gè)描述節(jié)點(diǎn)的虛擬DOM,因此可以直接執(zhí)renderer函數(shù)即可得到虛擬DOM
// MyComponent 是一個(gè)對(duì)象
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 是一個(gè)函數(shù)
function renderer(vnode, container) {
if (typeof vnode.tag === "string") {
// 說(shuō)明 vnode 描述的是標(biāo)簽元素
// mountElement內(nèi)部實(shí)現(xiàn)與下文的「實(shí)現(xiàn)renderer渲染器邏輯一致」
mountElement(vnode, container);
} else if (typeof vnode.tag === "function") {
// 說(shuō)明 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渲染為特定平臺(tái)上的真實(shí)DOM,如在瀏覽器平臺(tái)上,渲染器會(huì)將虛擬DOM渲染為真實(shí)DOM - render表示
渲染,是執(zhí)行的動(dòng)作 - 渲染器不僅用來(lái)
渲染,還可以用來(lái)執(zhí)行其他操作,如激活已有DOM、SSR等;是更寬泛的概念,renderer包含render; - 渲染器除了普通的
創(chuàng)建節(jié)點(diǎn)功能外,還需要實(shí)現(xiàn)精確的定點(diǎn)更新對(duì)應(yīng)變化的虛擬DOM到界面上,其內(nèi)部原理簡(jiǎn)單,歸根結(jié)底就是通過(guò)熟悉的DOM操作API來(lái)完成渲染工作
實(shí)現(xiàn)renderer渲染器
function renderer(vnode, container) {
// 使用 vnode.tag 作為標(biāo)簽名稱創(chuàng)建對(duì)應(yīng)的 DOM 元素
const el = document.createElement(vnode.tag);
// 遍歷 vnode.props,將屬性、事件添加到 DOM 元素
for (const key in vnode.props) {
if (/^on/.test(key)) {
// 如果 key 以 on 開(kāi)頭,說(shuō)明它是事件
el.addEventListener(
key.substr(2).toLowerCase(), // 事件名稱 onClick ---> click
vnode.props[key] // 事件處理函數(shù)
);
}
}
// 處理 children
if (typeof vnode.children === "string") {
// 如果 children 是字符串,說(shuō)明它是元素的文本子節(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)建元素:對(duì)應(yīng)虛擬DOM中的Tag屬性
- 為創(chuàng)建的元素添加屬性和事件:對(duì)應(yīng)虛擬DOM中的Props屬性
- 添加子元素:對(duì)應(yīng)虛擬DOM中的children屬性
- 這里的children可以是數(shù)組或字符串,數(shù)組時(shí)需要以剛才新建的DOM節(jié)點(diǎn)為父節(jié)點(diǎn)進(jìn)行回調(diào)渲染器renderer進(jìn)行遞歸渲染,當(dāng)是普通字符串時(shí)表明子節(jié)點(diǎn)是一個(gè)普通文本,直接調(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)渲染到頁(yè)面上
- 相當(dāng)于Vue2中的template標(biāo)簽
- Portal
- 允許將其中的內(nèi)容渲染到任何地方
渲染器階段分析
mount掛載階段
mount就是將VNode掛載到真實(shí)DOM上
patch階段
patch就是使用新的VNode和舊的VNode進(jìn)行對(duì)比,用最少的資源實(shí)現(xiàn)DOM更新,也叫「打補(bǔ)丁」
簡(jiǎn)單實(shí)現(xiàn)
渲染器接受兩個(gè)參數(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 不存在,此時(shí)渲染的是上一步中的節(jié)點(diǎn)值,說(shuō)明是卸載(unmount)操作
container.innerHTML = "";
}
}
// 把 vnode 存儲(chǔ)到 container._vnode 下,即后續(xù)渲染中的舊 vnode
container._vnode = vnode;
}
return {
render,
};
}
編譯器
編譯器在模板編譯時(shí)可以識(shí)別哪些是靜態(tài)屬性,哪些是動(dòng)態(tài)屬性,從而在編譯生成代碼時(shí)可以將這些信息附加到編譯后代碼中,這樣可以避免渲染器花費(fèi)力氣去尋找變更點(diǎn)
相關(guān)概念
編譯器和渲染器一樣,不同之處在于編譯器是將模板編譯成渲染函數(shù),而模板就是一個(gè)普通的字符串,編譯器會(huì)分析該字符串并生成一個(gè)功能與之相同的渲染函數(shù);
模板在Vue中體現(xiàn)在.vue文件中的<template>標(biāo)簽的內(nèi)容,本身.vue文件就是一個(gè)組件,編譯器會(huì)將模板內(nèi)容編譯成渲染函數(shù)并添加到<script>標(biāo)簽的組件對(duì)象上;
<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é)
通過(guò)將渲染器和編譯器相配合,從而達(dá)到進(jìn)一步提升性能的目的
- 虛擬DOM和模板都可以描述UI,虛擬DOM比模板更加靈活,模板比虛擬DOM更加直觀
組件的實(shí)現(xiàn)需要依賴于渲染器,模板的編譯需要依賴于編譯器,而且編譯后的代碼是根據(jù)渲染器和虛擬DOM的設(shè)計(jì)決定的,因此Vue中各個(gè)模塊之間是相互關(guān)聯(lián)、相互制約的;
模板工作原理:無(wú)論是模板還是直接手寫(xiě)渲染函數(shù),對(duì)于一個(gè)組件來(lái)說(shuō),其渲染內(nèi)容最終都是通過(guò)渲染函數(shù)產(chǎn)生的,然后渲染器再將渲染函數(shù)返回 的虛擬DOM渲染為真實(shí)DOM
以上就是Vue3渲染器與編譯器深入淺析的詳細(xì)內(nèi)容,更多關(guān)于Vue3渲染器編譯器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
如何解決Vue3組合式API模式下動(dòng)態(tài)組件不渲染問(wèn)題
這篇文章主要介紹了如何解決Vue3組合式API模式下動(dòng)態(tài)組件不渲染問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教<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ù)覽組件方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09
vue+elementUI實(shí)現(xiàn)動(dòng)態(tài)面包屑
這篇文章主要為大家詳細(xì)介紹了vue+elementUI實(shí)現(xiàn)動(dòng)態(tài)面包屑,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
vue+element下日期組件momentjs轉(zhuǎn)換賦值問(wèn)題解決
這篇文章主要介紹了vue+element下日期組件momentjs轉(zhuǎn)換賦值問(wèn)題,記錄下使用momentjs轉(zhuǎn)換日期字符串賦值給element的日期組件報(bào)錯(cuò)問(wèn)題,需要的朋友可以參考下2024-02-02
深入探討Vue計(jì)算屬性與監(jiān)聽(tīng)器的區(qū)別和用途
在Vue的開(kāi)發(fā)中,計(jì)算屬性(Computed Properties)和監(jiān)聽(tīng)器(Watchers)是兩種非常重要的概念,它們都用于響應(yīng)式地處理數(shù)據(jù)變化,本文將帶你深入了解計(jì)算屬性和監(jiān)聽(tīng)器的區(qū)別,以及在何時(shí)使用它們,感興趣的朋友可以參考下2023-09-09
VUE登錄注冊(cè)頁(yè)面完整代碼(直接復(fù)制)
這篇文章主要給大家介紹了關(guān)于VUE登錄注冊(cè)頁(yè)面的相關(guān)資料,在Vue中可以使用組件來(lái)構(gòu)建登錄注冊(cè)頁(yè)面,文中通過(guò)圖文以及代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12

