vue3源码分析:vnode标记系统
2026-07-23 14:29:32
vue3 使用VNode(虚拟节点,树结构的js对象) 对真实 DOM 进行描述,更新页面时就是通过对比新旧 VNode 树,最小化操作实现dom的修改。 结构类似如下:
{
anchor:null,
appContext:
{app: {…}, config: {…}, mixins: Array(0), components: {…}, directives: {…}, …},
children: null,
component:{uid: 0, vnode: {…}, type: {…}, parent: null, appContext: {…}, …},
ctx: null,
dirs: null,
dynamicChildren: null,
dynamicProps: null,
el: text,
key: null,
patchFlag:0,
props: null,
ref: null,
scopeId: null,
shapeFlag: 4,
slotScopeIds: null,
ssContent: null,
ssFallback: null,
staticCount: 0,
suspense: null,
target:null,
targetAnchor: null,
targetStart: null,
transition: null,
type:{
template:
'<div v-on:click="setAge">修改年龄</div><div>{{age}}</div><div>Hello Vue</div>',
setup: ƒ,
render: ƒ
},
__v_isVNode: true,
__v_skip: true
}
vnode使用属性type描述了当前vnode节点的具体内容,使用属性shapeFlag描述内容类型,shapeFlag所有值如下:
export enum ShapeFlags {
ELEMENT = 1, //普通 DOM 元素
FUNCTIONAL_COMPONENT = 1 << 1,//函数式组件
STATEFUL_COMPONENT = 1 << 2, //普通组件
TEXT_CHILDREN = 1 << 3,//子元素文本 例如:<div>Hello</div>
ARRAY_CHILDREN = 1 << 4, //子元素列表//例:<div><span/><span/></div>
SLOTS_CHILDREN = 1 << 5,//组件插槽
TELEPORT = 1 << 6,
SUSPENSE = 1 << 7,//异步组件
COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,//组件应该缓存
COMPONENT_KEPT_ALIVE = 1 << 9,//已经被缓存过
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT,//所有组件类型
}
位运算符 << , | , & 含义:
"<<"左移运算符
a << b 表示把数字 a 的二进制位向左移动 b 位
例:1 << 2
数字1的二进制 是 00000001 ,左移 2 位 即为 00000100
"|" 按位或
00000001
|
00000100
------------
00000101
"&" 按位与
00001001
&
00000001
---------
00000001
vue为什么不用十进制表示呢?如下:
1 << 2 = 00000100(二进制) = 4(十进制)
因为vue中需要 组合shapeFlag,例如 ELEMENT |TEXT_CHILDREN 假设:
const ELEMENT = 1
const TEXT_CHILDREN = 2
const COMPONENT = 3
此时一个vnode 的shapeFlag 需要 ELEMENT |TEXT_CHILDREN 来描述: 那么组合标识ELEMENT+TEXT_CHILDREN = 3 此时就与COMPONENT =3冲突了,分不清 3 具体表示什么。
而使用二进制,每个 ShapeFlag 又占用唯一的 bit 位。因此不论单个ShapeFlag ,还是组合ShapeFlag, 最终的值 都是唯一,且知道具体由哪些ShapeFlag组成。
在patch过程中 需要大量对当前节点的判断,二进制运算效率高于js函数判断。