EP61 - 渲染機制

閱讀時間約 12 分鐘
Rendering Mechanism 一直不懂這是什麼!
希望多了解一點~應該對框架會更熟悉吧www

Vue 如何將模板轉換為實際的 DOM 節點?
Vue 如何高效地更新這些 DOM 節點?
我們將深入探討 Vue 的內部渲染機制,試圖解答這些問題。

虛擬 DOM - Virtual DOM

您可能聽說過 "虛擬 DOM" 這個術語,Vue 的渲染系統就是基於此概念的。

虛擬 DOM (VDOM) 是一種編程概念,其中將 UI 的理想或“虛擬”表示保存在內存中,並與“真實” DOM 同步。這一概念由 React 首創,並在許多其他框架中採用了不同的實現,包括 Vue。

虛擬 DOM 更像是一種模式而不是特定技術,所以沒有一個標準的實現。我們可以用一個簡單的例子來說明這個概念:

const vnode = {
type: 'div',
props: {
id: 'hello'
},
children: [
/* more vnodes */
]
}

在這裡,vnode 是一個普通的 JavaScript 物件(“虛擬節點”),代表一個 <div> 元素。它包含創建實際元素所需的所有信息,也包含更多的子 vnode,使其成為虛擬 DOM 樹的根節點。

運行時渲染器可以遍歷虛擬 DOM 樹並從中構建真實的 DOM 樹。這個過程稱為掛載(mount)。

如果我們有兩個虛擬 DOM 樹的副本,渲染器還可以遍歷並比較這兩棵樹,找出差異,並將這些更改應用到實際 DOM 中。這個過程稱為修補(patch),也稱為“差異算法(diffing)”或“協調過程(reconcilication)”。

虛擬 DOM 的主要優勢在於,它使開發者能夠以聲明的方式程式化地創建、檢查和組合所需的 UI 結構,而將直接 DOM 操作留給渲染器處理。

渲染管線 - Render Pipeline

在高層次上,這是 Vue 元件掛載時發生的事情:

  1. 編譯(compile):Vue 模板被編譯成渲染函數,即返回虛擬 DOM 樹的函數。這個步驟可以在構建過程中提前完成,也可以通過運行時編譯器即時完成。
  2. 掛載(mount):運行時渲染器調用渲染函數,遍歷返回的虛擬 DOM 樹,並根據它創建實際的 DOM 節點。這一步作為一個響應式效果進行,所以它會跟踪所有使用的響應式依賴。
  3. 修補(patch):當掛載期間使用的依賴發生變化時,效果會重新運行。這次會創建一個新的、更新的虛擬 DOM 樹。運行時渲染器遍歷新樹,將其與舊樹進行比較,並應用必要的更新到實際 DOM。
raw-image

模板與渲染函數 - Templates vs. Render Functions

Vue 模板被編譯成虛擬 DOM 渲染函數。Vue 也提供了 API,允許我們跳過模板編譯步驟,直接編寫渲染函數。渲染函數在處理高度動態的邏輯時比模板更靈活,因為您可以使用 JavaScript 的全部功能來操作 vnode。

那麼,為什麼 Vue 默認推薦使用模板呢?這有多個原因:

  1. 模板更接近實際的 HTML。這使得重用現有的 HTML 片段、更好地應用可訪問性最佳實踐、使用 CSS 進行樣式設計,以及設計師更容易理解和修改變得更簡單。
  2. 模板由於其更確定的語法,更容易進行靜態分析。這允許 Vue 的模板編譯器應用許多編譯時優化來提高虛擬 DOM 的性能(我們會在下面討論)。

實際應用中,模板對於大多數用例已經足夠。渲染函數通常只在需要處理高度動態渲染邏輯的可重用元件中使用。渲染函數的使用在 渲染函數與 JSX 中有更詳細的討論。

編譯器告知的虛擬 DOM - Compiler-Informed Virtual DOM

React 和大多數其他虛擬 DOM 的實現都是純運行時的:協調算法無法對即將到來的虛擬 DOM 樹做出任何假設,所以它必須完全遍歷樹並對每個 vnode 的屬性進行差異比較以確保正確性。此外,即使樹的一部分從未改變,每次重新渲染時也總是為它們創建新的 vnode,導致不必要的內存壓力。這是對虛擬 DOM 最常見的批評之一:某種程度上的強力協調過程在效率上做出了妥協,以換取聲明性和正確性。

但不必如此。在 Vue 中,框架控制著編譯器和運行時。這使我們能夠實現許多僅僅是緊密耦合的渲染器才能利用的編譯時優化。編譯器可以靜態分析模板,並在生成的代碼中留下提示,以便運行時可以盡可能地採取快捷方式。同時,我們仍然保留了用戶在邊緣情況下降級到渲染函數層的能力。我們稱這種混合方法為 編譯器告知的虛擬 DOM。

下面,我們將討論 Vue 模板編譯器為提高虛擬 DOM 運行時性能所做的一些主要優化。

靜態提升 - Static Hoisting

模板中通常會有一些部分不包含任何動態綁定:

<div>
<div>foo</div> <!-- 提升 -->
<div>bar</div> <!-- 提升 -->
<div>{{ dynamic }}</div>
</div>

Inspect in Template Explorer

foo 和 bar div 是靜態的,每次重新渲染時重新創建 vnode 並對它們進行比較是沒有必要的。Vue 編譯器會自動將它們的 vnode 創建調用提升到渲染函數之外,並在每次渲染時重用相同的 vnode。渲染器還能夠在發現舊 vnode 和新 vnode 是同一個時完全跳過它們的比較。

此外,當有足夠多的連續靜態元素時,它們會被壓縮成一個包含所有這些節點的純 HTML 字符串的“靜態 vnode”(示例)。這些靜態 vnode 通過直接設置 innerHTML 來掛載。它們還在初始掛載時緩存其對應的 DOM 節點——如果相同的內容在應用程序的其他地方重用,會使用原生的 cloneNode() 創建新 DOM 節點,這是非常高效的。

修補標誌 - Patch Flags

對於具有動態綁定的單個元素,我們還可以在編譯時推斷出很多信息:

<!-- 只有 class 綁定 -->
<div :class="{ active }"></div>

<!-- 只有 id 和 value 綁定 -->
<input :id="id" :value="value">

<!-- 只有文本子元素 -->
<div>{{ dynamic }}</div>

Inspect in Template Explorer

在為這些元素生成渲染函數代碼時,Vue 會直接在 vnode 創建調用中編碼每個元素需要的更新類型:

createElementVNode("div", {
class: _normalizeClass({ active: _ctx.active })
}, null, 2 /* CLASS */)

最後一個參數 2 是修補標誌。一個元素可以有多個修補標誌,這些標誌將合併成一個數字。運行時渲染器可以使用位運算檢查這些標誌來確定是否需要做某些工作:

if (vnode.patchFlag & PatchFlags.CLASS /* 2 */) {
// 更新元素的 class
}

位運算檢查非常快。有了修補標誌,Vue 可以在更新具有動態綁定的元素時完成最少量的工作。

Vue 還編碼了 vnode 擁有的子元素類型。例如,一個具有多個根節點的模板表示為一個片段。在大多數情況下,我們可以確定這些根節點的順序永遠不會改變,所以這些信息也可以作為修補標誌提供給運行時:

export function render() {
return (_openBlock(), _createElementBlock(_Fragment, null, [
/* 子元素 */
], 64 /* STABLE_FRAGMENT */))
}

執行階段因此可以完全跳過根節點片段的子節點順序對齊過程。

樹狀展平 (Tree Flattening)​

再看一遍前面例子生成的代碼,你會注意到返回的虛擬 DOM 樹的根是通過一個特殊的 createElementBlock() 調用創建的:

export function render() {
return (_openBlock(), _createElementBlock(_Fragment, null, [
/* children */
], 64 /* STABLE_FRAGMENT */))
}

概念上,“塊”(block)是模板的一部分,具有穩定的內部結構。在這個例子中,整個模板只有一個塊,因為它不包含像 v-ifv-for 這樣的結構性指令。

每個塊會跟踪所有具有補丁標誌的子節點(不僅僅是直接子節點)。例如:

<div> <!-- 根塊 -->
<div>...</div> <!-- 不跟踪 -->
<div :id="id"></div> <!-- 跟踪 -->
<div> <!-- 不跟踪 -->
<div>{{ bar }}</div> <!-- 跟踪 -->
</div>
</div>

結果是一個扁平的數組,只包含動態的子節點:

div (根塊)
- div 帶有 :id 綁定
- div 帶有 {{ bar }} 綁定

當這個組件需要重新渲染時,它只需要遍歷這個扁平化的樹,而不是整棵樹。這被稱為樹狀展平 (Tree Flattening),它大大減少了虛擬 DOM 對齊過程中需要遍歷的節點數量。模板中的任何靜態部分都會被有效地跳過。

v-ifv-for 指令會創建新的塊節點:

<div> <!-- 根塊 -->
<div>
<div v-if> <!-- if-->
...
</div>
</div>
</div>

子塊會被跟踪在父塊的動態子節點數組中。這樣保持了父塊的穩定結構。

對 SSR Hydration 的影響​ - Impact on SSR Hydration

補丁標誌和樹狀展平也大大改善了 Vue 的 SSR Hydration 性能:

  • 單個元素的 Hydration 可以根據相應的 vnode 的補丁標誌進行快速處理。
  • 在 Hydration 過程中只需遍歷塊節點及其動態子節點,有效地在模板級別實現了部分 Hydration。
這一篇蠻不錯的耶~終於對虛擬DOM有一些概念了
了解它渲染機制感覺也很有趣~
avatar-img
2會員
71內容數
分享生活趣事~
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
卡關的人生 的其他內容
Vue 的反應性系統利用了 JavaScript 的 Proxy 和 getter/setter 機制,使得對物件屬性的變更能夠自動觸發視圖更新。這種無侵入性的設計使得狀態管理變得簡單且直觀,無需手動同步視圖。反應性的核心在於追蹤依賴項和觸發副作用。
Composition API 是 Vue 3 的一組 API,允許使用導入的函數編寫 Vue 組件,涵蓋反應性 API、生命週期鉤子和依賴注入。它主要在單文件組件的 <script setup> 語法中使用。
Vue 是一個靈活且可逐步採用的框架,適用於不同使用情境以平衡技術棧複雜度、開發者體驗和最終性能。Vue 可以作為獨立腳本文件使用,不需構建步驟,適合簡單前端邏輯。也可用來構建標準 Web 組件,嵌入到任何 HTML 頁面中。對於需要豐富互動性的應用,可構建單頁應用程式 (SPA)。
項目 API 涉及整體應用的全局屬性及配置,通常透過 TypeScript 的模組擴充來增強全局屬性,如 app.config.globalProperties。
在使用 Vue 組件 API 和 TypeScript 的開發中,通常會遇到類型錯誤和需要 debug 的情況。組件 API 下的 TypeScript 提供了更強的型別檢查和類型推斷功能,這有助於減少錯誤。
使用 TypeScript 開發 Vue 的好處包括:靜態類型檢查可減少運行時錯誤,提升代碼重構的安全性。TypeScript 提供更好的開發體驗,因為 IDE 支持類型自動完成功能。
Vue 的反應性系統利用了 JavaScript 的 Proxy 和 getter/setter 機制,使得對物件屬性的變更能夠自動觸發視圖更新。這種無侵入性的設計使得狀態管理變得簡單且直觀,無需手動同步視圖。反應性的核心在於追蹤依賴項和觸發副作用。
Composition API 是 Vue 3 的一組 API,允許使用導入的函數編寫 Vue 組件,涵蓋反應性 API、生命週期鉤子和依賴注入。它主要在單文件組件的 <script setup> 語法中使用。
Vue 是一個靈活且可逐步採用的框架,適用於不同使用情境以平衡技術棧複雜度、開發者體驗和最終性能。Vue 可以作為獨立腳本文件使用,不需構建步驟,適合簡單前端邏輯。也可用來構建標準 Web 組件,嵌入到任何 HTML 頁面中。對於需要豐富互動性的應用,可構建單頁應用程式 (SPA)。
項目 API 涉及整體應用的全局屬性及配置,通常透過 TypeScript 的模組擴充來增強全局屬性,如 app.config.globalProperties。
在使用 Vue 組件 API 和 TypeScript 的開發中,通常會遇到類型錯誤和需要 debug 的情況。組件 API 下的 TypeScript 提供了更強的型別檢查和類型推斷功能,這有助於減少錯誤。
使用 TypeScript 開發 Vue 的好處包括:靜態類型檢查可減少運行時錯誤,提升代碼重構的安全性。TypeScript 提供更好的開發體驗,因為 IDE 支持類型自動完成功能。
你可能也想看
Google News 追蹤
Thumbnail
Hi 我是 VK~ 在 8 月底寫完〈探索 AI 時代的知識革命:NotebookLM 如何顛覆學習和創作流程?〉後,有機會在 INSIDE POSSIBE 分享兩次「和 NotebookLM 協作如何改變我學習和創作」的主題,剛好最近也有在許多地方聊到關於 NotebookLM 等 AI 工具
Thumbnail
國泰CUBE App 整合外幣換匯、基金、證券等服務,提供簡便、低成本的美股定期定額投資解決方案。 5分鐘開戶、低投資門檻,幫助新手輕鬆進軍國際股市;提供人氣排行榜,讓投資人能夠掌握市場趨勢。
Thumbnail
這是張老師的第三本書,我想前二本應該也有很多朋友們都有讀過,我想絕對是受益良多,而這次在書名上就直接點出,著重在從投資的角度來切入
Thumbnail
CSS 盒模型是理解和設計網頁佈局的核心概念。它包括元素的內容、填充、邊框和外邊距。
Thumbnail
各位使用 Vue.js 開發的小夥伴們,你們都怎麼實作父子層組件資料的雙向綁定呢?如果你還在寫 prop + emit 的話,不妨進來看看吧。
Thumbnail
※ 原本狀態:伺服器渲染 這是 MVC 架構下的 request / response 示意圖,在這張圖呈現的架構裡,畫面和資料都由同一個架構處理。 伺服器渲染流程: 瀏覽器針對特定網址送出請求。 路由器解析請求後,轉接給對應的 controller。 controller 按照要求,透過
Thumbnail
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
一開始你先把你的專案push上去後,修改vite.config.ts ,要在裡面新增  base: "/Cart/" (/放自己的專案名稱/) build: {outDir: "docs"}, 接下來你要去你的github setting 裡面 -> Page ->選Deploy fro
Thumbnail
※ 視圖模板 視圖模板(View Templates) 是在 MVC 架構中負責展示數據的 HTML 文件,包含模板語法,用於在渲染時插入實際數據。它們的主要目的是分離數據與展示邏輯,讓代碼更加模塊化和易於維護。 視圖模板設計和使用的核心理念,就是「重複的事情不要重複做、效益最大化、有效利用資源
Thumbnail
你好,在下最近在學習開發web,學了html css js,也得出一些心得,由於網路上已有許多教學,所以我會著重在如何開發出to do List,以及解釋我寫的程式碼。相關的教學我會直接貼網址。如果我有什麼地方出錯,或者是可以寫得更好,歡迎在下方留言,討論。 首先先介紹我的開發環境: 我用了vs
Thumbnail
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找
Thumbnail
Hi 我是 VK~ 在 8 月底寫完〈探索 AI 時代的知識革命:NotebookLM 如何顛覆學習和創作流程?〉後,有機會在 INSIDE POSSIBE 分享兩次「和 NotebookLM 協作如何改變我學習和創作」的主題,剛好最近也有在許多地方聊到關於 NotebookLM 等 AI 工具
Thumbnail
國泰CUBE App 整合外幣換匯、基金、證券等服務,提供簡便、低成本的美股定期定額投資解決方案。 5分鐘開戶、低投資門檻,幫助新手輕鬆進軍國際股市;提供人氣排行榜,讓投資人能夠掌握市場趨勢。
Thumbnail
這是張老師的第三本書,我想前二本應該也有很多朋友們都有讀過,我想絕對是受益良多,而這次在書名上就直接點出,著重在從投資的角度來切入
Thumbnail
CSS 盒模型是理解和設計網頁佈局的核心概念。它包括元素的內容、填充、邊框和外邊距。
Thumbnail
各位使用 Vue.js 開發的小夥伴們,你們都怎麼實作父子層組件資料的雙向綁定呢?如果你還在寫 prop + emit 的話,不妨進來看看吧。
Thumbnail
※ 原本狀態:伺服器渲染 這是 MVC 架構下的 request / response 示意圖,在這張圖呈現的架構裡,畫面和資料都由同一個架構處理。 伺服器渲染流程: 瀏覽器針對特定網址送出請求。 路由器解析請求後,轉接給對應的 controller。 controller 按照要求,透過
Thumbnail
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
一開始你先把你的專案push上去後,修改vite.config.ts ,要在裡面新增  base: "/Cart/" (/放自己的專案名稱/) build: {outDir: "docs"}, 接下來你要去你的github setting 裡面 -> Page ->選Deploy fro
Thumbnail
※ 視圖模板 視圖模板(View Templates) 是在 MVC 架構中負責展示數據的 HTML 文件,包含模板語法,用於在渲染時插入實際數據。它們的主要目的是分離數據與展示邏輯,讓代碼更加模塊化和易於維護。 視圖模板設計和使用的核心理念,就是「重複的事情不要重複做、效益最大化、有效利用資源
Thumbnail
你好,在下最近在學習開發web,學了html css js,也得出一些心得,由於網路上已有許多教學,所以我會著重在如何開發出to do List,以及解釋我寫的程式碼。相關的教學我會直接貼網址。如果我有什麼地方出錯,或者是可以寫得更好,歡迎在下方留言,討論。 首先先介紹我的開發環境: 我用了vs
Thumbnail
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找