持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情
回顾
上一篇讲到了从 createApp 这条线下去找,找到了 app 的属性,然后知道了在 mount 里面调用createVNode创建了根节点的 vnode,最后调用了render方法,对这个vnode 进行了渲染。
这篇我们来分析一下 createVNode
createVNode
createVNode 的位置在 /packages/runtime-core/src/vnode.ts
// packages/runtime-core/src/vnode.ts
function _createVNode(
type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
props: (Data & VNodeProps) | null = null,
children: unknown = null,
patchFlag: number = 0,
dynamicProps: string[] | null = null,
isBlockNode = false
): VNode {
if (isVNode(type)) {
const cloned = cloneVNode(type, props, true /* mergeRef: true */)
if (children) {
normalizeChildren(cloned, children)
}
return cloned
}
// class & style normalization.
if (props) {
// for reactive or proxy objects, we need to clone it to enable mutation.
props = guardReactiveProps(props)!
let { class: klass, style } = props
if (klass && !isString(klass)) {
props.class = normalizeClass(klass)
}
if (isObject(style)) {
// reactive state objects need to be cloned since they are likely to be
// mutated
if (isProxy(style) && !isArray(style)) {
style = extend({}, style)
}
props.style = normalizeStyle(style)
}
}
// encode the vnode type information into a bitmap
// 注意 type 有可能是 string 也有可能是对象
// 如果是对象的话,那么就是用户设置的 options
// type 为 string 的时候
// createVNode("div")
// type 为组件对象的时候
// createVNode(App)
const shapeFlag = isString(type)
? ShapeFlags.ELEMENT
: __FEATURE_SUSPENSE__ && isSuspense(type)
? ShapeFlags.SUSPENSE
: isTeleport(type)
? ShapeFlags.TELEPORT
: isObject(type)
? ShapeFlags.STATEFUL_COMPONENT
: isFunction(type)
? ShapeFlags.FUNCTIONAL_COMPONENT
: 0
return createBaseVNode(
type,
props,
children,
patchFlag,
dynamicProps,
shapeFlag,
isBlockNode,
true
)
}
函数中初始化了一些数据,并且对传入的组件做了一些处理,如果传入的组件已经是一个 VNode 节点,则直接对其克隆并返回。
通过 type 确定了shapeFlag属性,该属性标识了该VNode的类型,最后调用了createBaseVNode方法。
// packages/runtime-core/src/vnode.ts
function createBaseVNode(
type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
props: (Data & VNodeProps) | null = null,
children: unknown = null,
patchFlag = 0,
dynamicProps: string[] | null = null,
shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
isBlockNode = false,
needFullChildrenNormalization = false
) {
const vnode = {
__v_isVNode: true,
__v_skip: true,
type,
props,
key: props && normalizeKey(props),
ref: props && normalizeRef(props),
scopeId: currentScopeId,
slotScopeIds: null,
children,
component: null,
suspense: null,
ssContent: null,
ssFallback: null,
dirs: null,
transition: null,
el: null,
anchor: null,
target: null,
targetAnchor: null,
staticCount: 0,
shapeFlag,
patchFlag,
dynamicProps,
dynamicChildren: null,
appContext: null
} as VNode
if (needFullChildrenNormalization) {
normalizeChildren(vnode, children)
} else if (children) {
vnode.shapeFlag |= isString(children)
? ShapeFlags.TEXT_CHILDREN
: ShapeFlags.ARRAY_CHILDREN
}
return vnode
}
这个方法真正的生成了 VNode 对象,并对子节点进行了一些处理。
所以这块的流程是 createVNode => _createVNode => createBaseVNode => VNode
ShapeFlag
我们接下来看下 VNode 的 ShapeFlag 标志。
export const enum ShapeFlags {
ELEMENT = 1,
FUNCTIONAL_COMPONENT = 1 << 1,
STATEFUL_COMPONENT = 1 << 2,
TEXT_CHILDREN = 1 << 3,
ARRAY_CHILDREN = 1 << 4,
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
}
我们注意到 ShapeFlag 基本都是通过十进制数字 1 进行左位移操作不同的位数得到的。然后根据其中部分属性派生出了 COMPONENT 类型,
因为有状态组件 和 函数式组件都是“组件”,用 COMPONENT 表示。
那为什么这里要用位操作来当作标识符呢?有什么好处?
我们先来感受一些,后续代码中是如何通过这些标识符判断的。
if (shapeFlag & ShapeFlags.ELEMENT) {
} else if (shapeFlag & ShapeFlags.COMPONENT) {
} else if (shapeFlag & ShapeFlags.TELEPORT) {
}
可以看到这里也采用位运算来判断种类,因为在一次挂载任务中这种判断很可能大量的进行,使用位运算在一定程度上再次拉升了运行时性能,相比而言,位掩码的运算速度远比直接判断 === 运算的高,除却函数调用带来额外开销,位运算发生于系统底层。
所以使用位操作的好处就是效率更高。
我们把 ShapeFlags 整理成表格,这样就能很清楚的理解为什么可以用位操作符 & 来判断。
| ShapeFlags | 左移运算 | 32位的bit序列 |
|---|---|---|
| ELEMENT | 无 | 0000000001 |
| FUNCTIONAL_COMPONENT | 1 << 1 | 0000000010 |
| STATEFUL_COMPONENT | 1 << 2 | 0000000100 |
| TEXT_CHILDREN | 1 << 3 | 0000001000 |
| ARRAY_CHILDREN | 1 << 4 | 0000010000 |
| SLOTS_CHILDREN | 1 << 5 | 0000100000 |
| TELEPORT | 1 << 6 | 0001000000 |
| SUSPENSE | 1 << 7 | 0010000000 |
| COMPONENT_SHOULD_KEEP_ALIVE | 1 << 8 | 0100000000 |
| COMPONENT_KEPT_ALIVE | 1 << 9 | 1000000000 |
而根据 FUNCTIONAL_COMPONENT 和 STATEFUL_COMPONENT 的值我们可以得到 COMPONENT
| ShapeFlags | 左移运算 | 32位的bit序列 |
|---|---|---|
| COMPONENT | 无 | 0000001 10 |
所以可以看出只有 ShapeFlags.FUNCTIONAL_COMPONENT 和 ShapeFlags.STATEFUL_COMPONENT 与 ShapeFlags.COMPONENT 进行位与(&)运算,才会得到非零值,即为真。
小结
这节我们分析了VNode的生成,并研究了为什么 VNode 的标识符使用位操作进行判断。