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