[vue3] patchFlags与位运算
cnblogs 2024-08-04 08:11:00 阅读 55
Vue 3在编译template过程中,会通过patchFlags优化虚拟DOM更新,提升性能。这些标志通过位运算进行操作,包括动态文本、类、样式、属性、静态提升等。patchFlags的使用极大地提高了diff算法的效率。
<code>Vue3在编译template
的过程中会分析模板中的动态部分和静态部分,并标记相应的flag
,用于在运行时优化虚拟DOM的更新。
- <li>
Transform
:对AST
进行转换和优化,包括识别动态节点和静态节点;CodeGeneration
:将转换后的AST
生成渲染函数,这个阶段会生成patchFlags
。
Parse
:将模板字符串解析成AST
; 在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 :>Content</div>
</template>
<script>
export default {
data() {
return {
dynamicClass: 'active'
}
}
}
</script>
STYLE
:表示元素具有动态样式。<template>
<div :>Content</div>
</template>
<script>
export default {
data() {
return {
dynamicStyle: {
color: 'red'
}
}
}
}
</script>
PROPS
:表示元素具有非class
/style
的动态属性。也可以用于具有任何动态属性的组件。<template>
<input :value="dynamicValue" />code>
</template>
<script>
export default {
data() {
return {
dynamicValue: 'Hello'
}
}
}
li></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"code>,在服务器端渲染时,事件绑定不会被实际添加,客户端加载后需要将事件监听器正确绑定到元素上。
<template>
<button @click="handleClick">Click me</button>code>
</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">code>
<child-component :data="someData" />code>
</template>
</parent-component>
</template>
<script>
export default {
data() {
return {
dynamicSlotName: 'defaultSlot',
someData: { message: 'Hello, World!' }
}
}
}
li></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" />code>
</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__
- <li>可以看到
<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" />code>使用了
v-model
指令,被标记了NEED_PATCH
;li>- 在
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)
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。