Vue组件渲染函数及VNode生成函数h()、createVNode()
Posted by Mars . Modified at
源文件目录:
packages/runtime-core/src/vnode.ts
packages/runtime-core/src/h.ts
Vue组件实现模板解析渲染的几种方式
- 单文件组件(SFC)中,Vue组件可以使用定义渲染模板;
- 任何组件都可以通过配置中tempalte property传入模板字符串定义渲染模板;
- 定义render()方法,作为组件的渲染函数进行纯JS渲染。
VNode的TS类型定义源码
VNode中常见的重要属性如下:
- type: VNode的类型。(代表类型的字符串,VNode对象,Component对象,Text类型Symbol,Static类型Symbol,Comment类型Symbol,Fragment类型Symbol等)
- props: VNode本身的属性。官方这里可选定义了:key,ref,和VNode的一些生命周期钩子:onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted等。Props可以在h()函数的第二个参数传入。
- children: 子VNode;
- key: VNode的唯一标志符;
- el: VNode在DOM中挂载的元素对象
// packages/runtime-core/src/vnode.ts
export interface VNode<
HostNode = RendererNode,
HostElement = RendererElement,
ExtraProps = { [key: string]: any }
> {
/**
* @internal
*/
// 判断是否是VNode的内部标志:__v_isVNode
__v_isVNode: true
/**
* @internal
*/
// VNode设置为永远不是响应式的
[ReactiveFlags.SKIP]: true
// VNode 的类型:
// export type VNodeTypes =
// | string
// | VNode
// | Component
// | typeof Text
// | typeof Static
// | typeof Comment
// | typeof Fragment (Fragment是Vue3引入的新节点类型,用来包裹多个根元素组件,让用户可以不用强制创建单一根元素组件。)
// | typeof TeleportImpl
// | typeof SuspenseImpl
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 patchsuspense` ydration) 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
dynamicProps: string[] | null
dynamicChildren: VNode[] | null
// application root node only
appContext: AppContext | null
}
h()函数
从源码上看,h()函数主要是对不同的传参形式进行了重载。在内部,还是调用createNode()函数。
// 这里定义了一系列h()函数的重载形式,用来让用户更容易使用。
// h()函数用来生成VNode,用户用来在组件中手动编写render()函数方法,用来代替template。
// !!————————从这里开始都是重载编写部分——————————————!!
// 传入字符串类型,生成DOM元素
// 可以传入两个参数,也可以传入三个参数。对应下面第一个和第二个重载形式。
export function h(type: string, children?: RawChildren): VNode
export function h(
type: string,
props?: RawProps | null,
children?: RawChildren | RawSlots
): VNode
// 传入文本类型符号或注释类型符号,生成对应VNode:
// export const Text = Symbol(__DEV__ ? 'Text' : undefined)
// export const Comment = Symbol(__DEV__ ? 'Comment' : undefined)
// export const Static = Symbol(__DEV__ ? 'Static' : undefined)
export function h(
type: typeof Text | typeof Comment,
children?: string | number | boolean
): VNode
export function h(
type: typeof Text | typeof Comment,
props?: null,
children?: string | number | boolean
): VNode
// 下面是特殊类型节点的生成,不重点了解。
// 生成fragment类型VNode
export function h(type: typeof Fragment, children?: VNodeArrayChildren): VNode
export function h(
type: typeof Fragment,
props?: RawProps | null,
children?: VNodeArrayChildren
): VNode
// teleport (target prop is required)
export function h(
type: typeof Teleport,
props: RawProps & TeleportProps,
children: RawChildren
): VNode
// suspense 组件类型节点
export function h(type: typeof Suspense, children?: RawChildren): VNode
export function h(
type: typeof Suspense,
props?: (RawProps & SuspenseProps) | null,
children?: RawChildren | RawSlots
): VNode
// functional component
export function h<P, E extends EmitsOptions = {}>(
type: FunctionalComponent<P, E>,
props?: (RawProps & P) | ({} extends P ? null : never),
children?: RawChildren | RawSlots
): VNode
// catch-all for generic component types
export function h(type: Component, children?: RawChildren): VNode
// concrete component
export function h<P>(
type: ConcreteComponent | string,
children?: RawChildren
): VNode
export function h<P>(
type: ConcreteComponent<P> | string,
props?: (RawProps & P) | ({} extends P ? null : never),
children?: RawChildren
): VNode
// 没有属性props的组件
export function h(
type: Component,
props: null,
children?: RawChildren | RawSlots
): VNode
// exclude `defineComponent` constructors
export function h<P>(
type: ComponentOptions<P>,
props?: (RawProps & P) | ({} extends P ? null : never),
children?: RawChildren | RawSlots
): VNode
// fake constructor type returned by `defineComponent` or class component
export function h(type: Constructor, children?: RawChildren): VNode
export function h<P>(
type: Constructor<P>,
props?: (RawProps & P) | ({} extends P ? null : never),
children?: RawChildren | RawSlots
): VNode
// fake constructor type returned by `defineComponent`
export function h(type: DefineComponent, children?: RawChildren): VNode
export function h<P>(
type: DefineComponent<P>,
props?: (RawProps & P) | ({} extends P ? null : never),
children?: RawChildren | RawSlots
): VNode
// !!————————重载编写部分结束——————————————!!
// 这里是h()函数的具体实现:可以看到就是根据不同的形式调用createVNode()
export function h(type: any, propsOrChildren?: any, children?: any): VNode {
const l = arguments.length
if (l === 2) {
if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
// single vnode without props
if (isVNode(propsOrChildren)) {
return createVNode(type, null, [propsOrChildren])
}
// props without children
return createVNode(type, propsOrChildren)
} else {
// omit props
return createVNode(type, null, propsOrChildren)
}
} else {
if (l > 3) {
children = Array.prototype.slice.call(arguments, 2)
} else if (l === 3 && isVNode(children)) {
children = [children]
}
return createVNode(type, propsOrChildren, children)
}
}
createVNode函数: h()函数调用的原始函数
// 根据是否是开发环境选择不同的方法,核心都是_createVNode函数。
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 {
// 判断传入的type是否合法(为falsy),不合法报错。
// 这时type会被设定为注释类型。
if (!type || type === NULL_DYNAMIC_COMPONENT) {
if (__DEV__ && !type) {
warn(`Invalid vnode type when creating vnode: ${type}.`)
}
type = Comment
}
// 判断传入的类型如果本身是VNode,就复制一个新的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.
if (isClassComponent(type)) {
type = type.__vccOpts
}
// 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
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
)
}
// VNode的初始值定义。包含VNode的所有属性参数。
const vnode: VNode = {
__v_isVNode: true,
[ReactiveFlags.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
}