Vue 3在編譯template過程中,會通過patchFlags優化虛擬DOM更新,提升性能。這些標誌通過位運算進行操作,包括動態文本、類、樣式、屬性、靜態提升等。patchFlags的使用極大地提高了diff演算法的效率。 ...
Vue3
在編譯template
的過程中會分析模板中的動態部分和靜態部分,並標記相應的flag
,用於在運行時優化虛擬DOM的更新。
Parse
:將模板字元串解析成AST
;Transform
:對AST
進行轉換和優化,包括識別動態節點和靜態節點;CodeGeneration
:將轉換後的AST
生成渲染函數,這個階段會生成patchFlags
。
在diff
過程中,遇到包含dynamicChildren
的塊時,diff
演算法會進入優化模式,跳過對靜態節點的處理從而優化了diff
的執行效率。
flag的種類
源碼位置:core/packages/shared/src/patchFlags.ts at main · vuejs/core (github.com)
export enum PatchFlags {
TEXT = 1,
CLASS = 1 << 1,
STYLE = 1 << 2,
PROPS = 1 << 3,
FULL_PROPS = 1 << 4,
NEED_HYDRATION = 1 << 5,
STABLE_FRAGMENT = 1 << 6,
KEYED_FRAGMENT = 1 << 7,
UNKEYED_FRAGMENT = 1 << 8,
NEED_PATCH = 1 << 9,
DYNAMIC_SLOTS = 1 << 10,
DEV_ROOT_FRAGMENT = 1 << 11,
HOISTED = -1,
BAIL = -2,
}
可以看到flag
使用二進位格式記錄的,並且每個標誌僅有一位為1
,這樣可以通過位運算獲知一個複合狀態里包含哪些狀態。
flag含義
-
TEXT
:表示元素具有動態的textContent
。<div>{{ dynamicText }}</div>
-
CLASS
:表示元素具有動態的類綁定。<template> <div :class="dynamicClass">Content</div> </template> <script> export default { data() { return { dynamicClass: 'active' } } } </script>
-
STYLE
:表示元素具有動態樣式。<template> <div :style="dynamicStyle">Content</div> </template> <script> export default { data() { return { dynamicStyle: { color: 'red' } } } } </script>
-
PROPS
:表示元素具有非class
/style
的動態屬性。也可以用於具有任何動態屬性的組件。<template> <input :value="dynamicValue" /> </template> <script> export default { data() { return { dynamicValue: 'Hello' } } } </script>
-
FULL_PROPS
:表示具有動態鍵屬性的元素。當鍵變化時,總是需要完全差異檢查。<template> <div v-bind:[dynamicProp]="dynamicValue">Content</div> </template> <script> export default { data() { return { dynamicProp: 'id', dynamicValue: 'uniqueId' } } } </script>
-
NEED_HYDRATION
:表示該元素在客戶端渲染時,需要將屬性從靜態HTML
轉換為動態綁定。hydration
是指從伺服器端渲染(SSR
)的靜態內容中恢復出動態行為和狀態的過程。該元素不需要常規的虛擬 DOM 屬性更新,只需要在初始化時處理特定的屬性以恢復其動態行為。案例(事件監聽器):如
@click="handler"
,在伺服器端渲染時,事件綁定不會被實際添加,客戶端載入後需要將事件監聽器正確綁定到元素上。<template> <button @click="handleClick">Click me</button> </template> <script> export default { methods: { handleClick() { console.log('Button clicked!') } } } </script>
-
STABLE_FRAGMENT
:表示子元素順序不變的片段。 -
KEYED_FRAGMENT
:表示子元素的都帶有或部分帶有key
標註。 -
UNKEYED_FRAGMENT
:表示子元素沒有key
標註的片段。 -
NEED_PATCH
:表示不涉及class
、style
和props
但仍需觸發更新的情況,通常對應ref
、指令等使用場景。 -
DYNAMIC_SLOTS
:主要用於標識那些插槽內容或插槽名稱是動態變化的組件。帶有此標誌的組件在更新時會被強制更新,以確保插槽內容或名稱的變化能夠正確反映到 DOM 中。<template> <parent-component> <template :slot="dynamicSlotName"> <child-component :data="someData" /> </template> </parent-component> </template> <script> export default { data() { return { dynamicSlotName: 'defaultSlot', someData: { message: 'Hello, World!' } } } } </script>
-
DEV_ROOT_FRAGMENT
:表示用戶在template
的頂層寫了註釋而創建的flag
。僅用於開發環境,因為生產中會去除註釋。<template> <!-- Root level comment --> <div>Content</div> </template>
-
HOISTED
:表示提升的靜態虛擬節點。patch
過程可以跳過整個子樹,因為靜態內容永遠不需要更新。<p>Static content</p>
-
BAIL
:表示diff
演算法應退出優化模式,通常是對應用戶使用h
函數自定義渲染函數的情況。
示例代碼
vue3
有提供一個playground
可以查看編譯後的結果:Vue SFC Playground (vuejs.org)
簡單的代碼案例:
<script setup>
import { ref } from 'vue'
const msg = ref('Hello World!')
</script>
<template>
<p>static content</p>
<h1>{{ msg }}</h1>
<input v-model="msg" />
</template>
編譯後的JS
import { ref } from 'vue'
const __sfc__ = {
__name: 'App',
setup(__props, { expose: __expose }) {
__expose();
const msg = ref('Hello World!')
const __returned__ = { msg, ref }
Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true })
return __returned__
}
};
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, vModelText as _vModelText, withDirectives as _withDirectives, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
const _hoisted_1 = /*#__PURE__*/_createElementVNode("p", null, "static content", -1 /* HOISTED */)
function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
_hoisted_1,
_createElementVNode("h1", null, _toDisplayString($setup.msg), 1 /* TEXT */),
_withDirectives(_createElementVNode("input", {
"onUpdate:modelValue": _cache[0] || (_cache[0] = $event => (($setup.msg) = $event))
}, null, 512 /* NEED_PATCH */), [
[_vModelText, $setup.msg]
])
], 64 /* STABLE_FRAGMENT */))
}
__sfc__.render = render
__sfc__.__file = "src/App.vue"
export default __sfc__
- 可以看到
<p>static content</p>
被應用了靜態提升(Vue3
優化策略之一):
(靜態的flag
是-1
)
const _hoisted_1 = /*#__PURE__*/_createElementVNode("p", null, "static content", -1 /* HOISTED */)
<h1>{{ msg }}</h1>
由於有動態文本,被標記為TEXT
;<input v-model="msg" />
使用了v-model
指令,被標記了NEED_PATCH
;- 在
Vue2
里template
內部只能存在一個頂級節點,如果有多個要使用一個標簽囊括其中;Vue3
支持template
內部多個頂級節點,其實是框架幫我們套了一個fragment
;在上述代碼中由於這個fragment
內部元素的順序是固定的,因此被標記為STABLE_FRAGMENT
。
靜態提升
靜態提升是Vue3
的一種性能優化手段。如果有VNode
被標記為靜態節點,說明它的內容是固定不變的。那麼它的構建函數會被提升到渲染函數的外部,即只會被運行一次。
位運算的應用
在Vue3
中,這些flags
都是只有一位為1
,在這個前提下,可以通過位運算實現下麵兩種操作:
組合標誌
通過或運算組合標誌:
const combinedFlag = PatchFlags.TEXT | PatchFlags.STYLE; // 0001 | 0100 = 0101
檢查標誌
通過與運算檢查混合標誌是否存在某個base flag
:
const hasText = combinedFlag & PatchFlags.TEXT; // 0101 & 0001 = 0001 (truthy)
const hasClass = combinedFlag & PatchFlags.CLASS; // 0101 & 0010 = 0000 (falsy)
可以在源碼中看到patchFlag
和與運算的相關代碼:core/packages/runtime-core/src/renderer.ts at main · vuejs/core (github.com)