QA
- 什么是
VNode
,为什么需要VNode
? vue-next
中,如何实现的VNode
?
VNode
在了解了浏览器渲染流程之后,我们再来去看vnode
,vnode
定义了一种数据描述,它是对DOM
节点的一种描述,如果我们对DOM
进行操作,会产生渲染开销,当然这部分不是js
所产生的,而是由浏览器渲染所产生的,所以就诞生了Vitrual DOM
。
在vue-next
中,是通过一个对象去描述的,代码在packages/runtime-core/src/vnode.ts
,这是vnode
的类型定义。
export interface VNode<
HostNode = RendererNode,
HostElement = RendererElement,
ExtraProps = { [key: string]: any }
> {
/**
* @internal
*/
__v_isVNode: true // 标记为vnode
/**
* @internal
*/
[ReactiveFlags.SKIP]: true
type: VNodeTypes
props: (VNodeProps & ExtraProps) | null
key: string | number | null
ref: VNodeNormalizedRef | null
/**
* SFC only. This is assigned on vnode creation using currentScopeId
* which is set alongside currentRenderingInstance.
*/
scopeId: string | null
/**
* SFC only. This is assigned to:
* - Slot fragment vnodes with :slotted SFC styles.
* - Component vnodes (during patch/hydration) so that its root node can
* inherit the component's slotScopeIds
*/
slotScopeIds: string[] | null
children: VNodeNormalizedChildren
component: ComponentInternalInstance | null
dirs: DirectiveBinding[] | null // 指令
transition: TransitionHooks<HostElement> | null
// DOM
el: HostNode | null
anchor: HostNode | null // fragment anchor
target: HostElement | null // teleport target
targetAnchor: HostNode | null // teleport target anchor
staticCount: number // number of elements contained in a static vnode
// suspense
suspense: SuspenseBoundary | null
ssContent: VNode | null
ssFallback: VNode | null
// optimization only
shapeFlag: number // 类型标记
patchFlag: number // diff标记
dynamicProps: string[] | null
dynamicChildren: VNode[] | null
// application root node only 只限于根节点
appContext: AppContext | null
}
具体实现
let vnodeArgsTransformer:
| ((
args: Parameters<typeof _createVNode>,
instance: ComponentInternalInstance | null
) => Parameters<typeof _createVNode>)
| undefined
/**
* Internal API for registering an arguments transform for createVNode
* used for creating stubs in the test-utils
* It is *internal* but needs to be exposed for test-utils to pick up proper
* typings
*
* 自定义转换器,应该是用于其他库,如uniapp,这里不考虑
*/
export function transformVNodeArgs(transformer?: typeof vnodeArgsTransformer) {
vnodeArgsTransformer = transformer
}
const createVNodeWithArgsTransform = (
...args: Parameters<typeof _createVNode>
): VNode => {
// 首次渲染会执行args,args为组件
return _createVNode(
...(vnodeArgsTransformer
? vnodeArgsTransformer(args, currentRenderingInstance)
: args)
)
}
export const createVNode = (__DEV__
? createVNodeWithArgsTransform
: _createVNode) as typeof _createVNode
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 (!type || type === NULL_DYNAMIC_COMPONENT) {
if (__DEV__ && !type) {
warn(`Invalid vnode type when creating vnode: ${type}.`)
}
type = Comment
}
// 判断是否为Vnode,Vnode会存在__v_isVNode属性标识该节点为vnode
if (isVNode(type)) {
// createVNode receiving an existing vnode. This happens in cases like
// <component :is="vnode"/>
// #2078 make sure to merge refs during the clone instead of overwriting it
const cloned = cloneVNode(type, props, true /* mergeRef: true */)
if (children) {
normalizeChildren(cloned, children)
}
return cloned
}
// class component normalization.
// 是否为class组件,class组件会通过__vccOpts 作为标识
if (isClassComponent(type)) {
type = type.__vccOpts
}
// 2.x async/functional component compat 兼容v2的异步/函数 组件
if (__COMPAT__) {
type = convertLegacyComponent(type, currentRenderingInstance)
}
// class & style normalization.
if (props) {
// for reactive or proxy objects, we need to clone it to enable mutation.
if (isProxy(props) || InternalObjectKey in props) {
props = extend({}, 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 将vnode类型进行位运算编码
/**
* 注:
* suspence组件会多__isSuspense属性
* teleport会多一个__isTeleport属性
*
* if 如果是string 则为1
* else if 如果__FEATURE_SUSPENSE__ 并且是suspense组件 则为 128 __FEATURE_SUSPENSE__ 默认为true 通过rollup启动注入
* else if 如果是teleport组件为64
* else if 如果是对象,则为4
* else if 如果是函数,则为2
* else 否则为0
*/
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
if (__DEV__ && shapeFlag & ShapeFlags.STATEFUL_COMPONENT && isProxy(type)) {
type = toRaw(type)
warn(
`Vue received a Component which was made a reactive object. This can ` +
`lead to unnecessary performance overhead, and should be avoided by ` +
`marking the component with \`markRaw\` or using \`shallowRef\` ` +
`instead of \`ref\`.`,
`\nComponent that was made reactive: `,
type
)
}
const vnode: VNode = {
__v_isVNode: true,
__v_skip: true,
type,
props,
key: props && normalizeKey(props),
ref: props && normalizeRef(props),
scopeId: currentScopeId,
slotScopeIds: null,
children: null,
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
}
// validate key
if (__DEV__ && vnode.key !== vnode.key) {
warn(`VNode created with invalid key (NaN). VNode type:`, vnode.type)
}
normalizeChildren(vnode, children)
// normalize suspense children
if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
const { content, fallback } = normalizeSuspenseChildren(vnode)
vnode.ssContent = content
vnode.ssFallback = fallback
}
if (
shouldTrack > 0 &&
// avoid a block node from tracking itself
!isBlockNode &&
// has current parent block
currentBlock &&
// presence of a patch flag indicates this node needs patching on updates.
// component nodes also should always be patched, because even if the
// component doesn't need to update, it needs to persist the instance on to
// the next vnode so that it can be properly unmounted later.
(patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
// the EVENTS flag is only for hydration and if it is the only flag, the
// vnode should not be considered dynamic due to handler caching.
patchFlag !== PatchFlags.HYDRATE_EVENTS
) {
currentBlock.push(vnode)
}
return vnode
}
总结
上面是Vnode
的具体实现,vue-next
中会把模板转换成AST,并编译成_createVnode
函数从而进行渲染,具体后面再说。