[vue3] patchFlags與位運(yùn)算
Vue3在編譯template的過(guò)程中會(huì)分析模板中的動(dòng)態(tài)部分和靜態(tài)部分,并標(biāo)記相應(yīng)的flag,用于在運(yùn)行時(shí)優(yōu)化虛擬DOM的更新。
Parse:將模板字符串解析成AST;Transform:對(duì)AST進(jìn)行轉(zhuǎn)換和優(yōu)化,包括識(shí)別動(dòng)態(tài)節(jié)點(diǎn)和靜態(tài)節(jié)點(diǎn);CodeGeneration:將轉(zhuǎn)換后的AST生成渲染函數(shù),這個(gè)階段會(huì)生成patchFlags。
在diff過(guò)程中,遇到包含dynamicChildren的塊時(shí),diff算法會(huì)進(jìn)入優(yōu)化模式,跳過(guò)對(duì)靜態(tài)節(jié)點(diǎn)的處理從而優(yōu)化了diff的執(zhí)行效率。
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使用二進(jìn)制格式記錄的,并且每個(gè)標(biāo)志僅有一位為1,這樣可以通過(guò)位運(yùn)算獲知一個(gè)復(fù)合狀態(tài)里包含哪些狀態(tài)。
flag含義
-
TEXT:表示元素具有動(dòng)態(tài)的textContent。<div>{{ dynamicText }}</div> -
CLASS:表示元素具有動(dòng)態(tài)的類綁定。<template> <div :class="dynamicClass">Content</div> </template> <script> export default { data() { return { dynamicClass: 'active' } } } </script> -
STYLE:表示元素具有動(dòng)態(tài)樣式。<template> <div :style="dynamicStyle">Content</div> </template> <script> export default { data() { return { dynamicStyle: { color: 'red' } } } } </script> -
PROPS:表示元素具有非class/style的動(dòng)態(tài)屬性。也可以用于具有任何動(dòng)態(tài)屬性的組件。<template> <input :value="dynamicValue" /> </template> <script> export default { data() { return { dynamicValue: 'Hello' } } } </script> -
FULL_PROPS:表示具有動(dòng)態(tài)鍵屬性的元素。當(dāng)鍵變化時(shí),總是需要完全差異檢查。<template> <div v-bind:[dynamicProp]="dynamicValue">Content</div> </template> <script> export default { data() { return { dynamicProp: 'id', dynamicValue: 'uniqueId' } } } </script> -
NEED_HYDRATION:表示該元素在客戶端渲染時(shí),需要將屬性從靜態(tài)HTML轉(zhuǎn)換為動(dòng)態(tài)綁定。hydration是指從服務(wù)器端渲染(SSR)的靜態(tài)內(nèi)容中恢復(fù)出動(dòng)態(tài)行為和狀態(tài)的過(guò)程。該元素不需要常規(guī)的虛擬 DOM 屬性更新,只需要在初始化時(shí)處理特定的屬性以恢復(fù)其動(dòng)態(tài)行為。案例(事件監(jiān)聽(tīng)器):如
@click="handler",在服務(wù)器端渲染時(shí),事件綁定不會(huì)被實(shí)際添加,客戶端加載后需要將事件監(jiān)聽(tīng)器正確綁定到元素上。<template> <button @click="handleClick">Click me</button> </template> <script> export default { methods: { handleClick() { console.log('Button clicked!') } } } </script> -
STABLE_FRAGMENT:表示子元素順序不變的片段。 -
KEYED_FRAGMENT:表示子元素的都帶有或部分帶有key標(biāo)注。 -
UNKEYED_FRAGMENT:表示子元素沒(méi)有key標(biāo)注的片段。 -
NEED_PATCH:表示不涉及class、style和props但仍需觸發(fā)更新的情況,通常對(duì)應(yīng)ref、指令等使用場(chǎng)景。 -
DYNAMIC_SLOTS:主要用于標(biāo)識(shí)那些插槽內(nèi)容或插槽名稱是動(dòng)態(tài)變化的組件。帶有此標(biāo)志的組件在更新時(shí)會(huì)被強(qiáng)制更新,以確保插槽內(nèi)容或名稱的變化能夠正確反映到 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的頂層寫了注釋而創(chuàng)建的flag。僅用于開(kāi)發(fā)環(huán)境,因?yàn)樯a(chǎn)中會(huì)去除注釋。<template> <!-- Root level comment --> <div>Content</div> </template> -
HOISTED:表示提升的靜態(tài)虛擬節(jié)點(diǎn)。patch過(guò)程可以跳過(guò)整個(gè)子樹,因?yàn)殪o態(tài)內(nèi)容永遠(yuǎn)不需要更新。<p>Static content</p> -
BAIL:表示diff算法應(yīng)退出優(yōu)化模式,通常是對(duì)應(yīng)用戶使用h函數(shù)自定義渲染函數(shù)的情況。
示例代碼
vue3有提供一個(gè)playground可以查看編譯后的結(jié)果:Vue SFC Playground (vuejs.org)
簡(jiǎn)單的代碼案例:
<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>被應(yīng)用了靜態(tài)提升(Vue3優(yōu)化策略之一):
(靜態(tài)的flag是-1)
const _hoisted_1 = /*#__PURE__*/_createElementVNode("p", null, "static content", -1 /* HOISTED */)
<h1>{{ msg }}</h1>由于有動(dòng)態(tài)文本,被標(biāo)記為TEXT;<input v-model="msg" />使用了v-model指令,被標(biāo)記了NEED_PATCH;- 在
Vue2里template內(nèi)部只能存在一個(gè)頂級(jí)節(jié)點(diǎn),如果有多個(gè)要使用一個(gè)標(biāo)簽?zāi)依ㄆ渲校?code>Vue3支持template內(nèi)部多個(gè)頂級(jí)節(jié)點(diǎn),其實(shí)是框架幫我們套了一個(gè)fragment;在上述代碼中由于這個(gè)fragment內(nèi)部元素的順序是固定的,因此被標(biāo)記為STABLE_FRAGMENT。
靜態(tài)提升
靜態(tài)提升是Vue3的一種性能優(yōu)化手段。如果有VNode被標(biāo)記為靜態(tài)節(jié)點(diǎn),說(shuō)明它的內(nèi)容是固定不變的。那么它的構(gòu)建函數(shù)會(huì)被提升到渲染函數(shù)的外部,即只會(huì)被運(yùn)行一次。
位運(yùn)算的應(yīng)用
在Vue3中,這些flags都是只有一位為1,在這個(gè)前提下,可以通過(guò)位運(yùn)算實(shí)現(xiàn)下面兩種操作:
組合標(biāo)志
通過(guò)或運(yùn)算組合標(biāo)志:
const combinedFlag = PatchFlags.TEXT | PatchFlags.STYLE; // 0001 | 0100 = 0101
檢查標(biāo)志
通過(guò)與運(yùn)算檢查混合標(biāo)志是否存在某個(gè)base flag:
const hasText = combinedFlag & PatchFlags.TEXT; // 0101 & 0001 = 0001 (truthy)
const hasClass = combinedFlag & PatchFlags.CLASS; // 0101 & 0010 = 0000 (falsy)
可以在源碼中看到patchFlag和與運(yùn)算的相關(guān)代碼:core/packages/runtime-core/src/renderer.ts at main · vuejs/core (github.com)


浙公網(wǎng)安備 33010602011771號(hào)