本文主要是介绍Vue3源码梳理:运行时之基于h函数生成vnode的内部流程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
VNode 节点类型
- 对于vnode而言,具备很多节点类型
- vue源码中patch函数switch处理包含了好几种类型,常见类型如下
Text
:文本节点Comment
:注释节点Static
:静态dom节点Fragment
:包含多个根节点的模板被表示为一个片段 fragmentELEMENT
:DOM 节点COMPONENT
:组件TELEPORT
:新的内置组件SUSPENSE
:新的内置组件- …
h函数源码解析
1 )使用 h 函数,示例demo程序
<script src='../../dist/vue.global.js'></script><div id='app'></div><script>const { h } = Vueconst vnode = h('div', {class: 'test'}, 'hello render')console.log('vnode: ', vnode)
</script>
2 )对源码进行debug, 进入h函数
// Actual implementation
export function h(type: any, propsOrChildren?: any, children?: any): VNode {const l = arguments.lengthif (l === 2) {if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {// single vnode without propsif (isVNode(propsOrChildren)) {return createVNode(type, null, [propsOrChildren])}// props without childrenreturn createVNode(type, propsOrChildren)} else {// omit propsreturn 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)}
}
h 函数需要三个参数: type
, propsOrChildren
, children
- 注意第二个参数,
propsOrChildren
是一个对象,它可以是props
也可以是children
- 内部是基于传入的长度和类型来判断的,先长度(先基于2来判断的)后类型
- 最终返回
createVNode
- h函数本身只是对用户传递的参数的处理,其本质是
createVNode
- 使得
createVNode
调用时,更加的方便
3 ) createVNode 源码
export const createVNode = (__DEV__ ? createVNodeWithArgsTransform : _createVNode
) as typeof _createVNodefunction _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}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 itconst cloned = cloneVNode(type, props, true /* mergeRef: true */)if (children) {normalizeChildren(cloned, children)}if (isBlockTreeEnabled > 0 && !isBlockNode && currentBlock) {if (cloned.shapeFlag & ShapeFlags.COMPONENT) {currentBlock[currentBlock.indexOf(type)] = cloned} else {currentBlock.push(cloned)}}cloned.patchFlag |= PatchFlags.BAILreturn cloned}// class component normalization.if (isClassComponent(type)) {type = type.__vccOpts}// 2.x async/functional component compatif (__COMPAT__) {type = convertLegacyComponent(type, currentRenderingInstance)}// 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 } = propsif (klass && !isString(klass)) {props.class = normalizeClass(klass)}if (isObject(style)) {// reactive state objects need to be cloned since they are likely to be// mutatedif (isProxy(style) && !isArray(style)) {style = extend({}, style)}props.style = normalizeStyle(style)}}// encode the vnode type information into a bitmapconst 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: 0if (__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)}return createBaseVNode(type,props,children,patchFlag,dynamicProps,shapeFlag,isBlockNode,true)
}
- 其本质上触发的是
_createVNode
,进入它,有6个参数 type
,props
,children
,patchFlag
,dynamicProps
,isBlockNode
,我们主要关注其中三个参数type
props
children
- 代码往下走,看下
isVNode
函数,判断比较简单return value ? value.__v_isVNode === true: false
- 就是根据value的属性来的
- 之后在判断是否是class
- 在之后判断
props
,这里执行guardReactiveProps(props)
解析props的逻辑暂时不去管它- vue会有class和style的增强,这块先不去管它
- 之后走到一个比较复杂的三目运算
shapeFlag
- 它本身是一个枚举类,定义了很多类型
- 代码继续执行,直到
return createBaseVNode
createBaseVNode 函数
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 VNodeif (needFullChildrenNormalization) {normalizeChildren(vnode, children)// normalize suspense childrenif (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {;(type as typeof SuspenseImpl).normalize(vnode)}} else if (children) {// compiled element vnode - if children is passed, only possible types are// string or Array.vnode.shapeFlag |= isString(children)? ShapeFlags.TEXT_CHILDREN: ShapeFlags.ARRAY_CHILDREN}// validate keyif (__DEV__ && vnode.key !== vnode.key) {warn(`VNode created with invalid key (NaN). VNode type:`, vnode.type)}// track vnode for block treeif (isBlockTreeEnabled > 0 &&// avoid a block node from tracking itself!isBlockNode &&// has current parent blockcurrentBlock &&// 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.(vnode.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.vnode.patchFlag !== PatchFlags.HYDRATE_EVENTS) {currentBlock.push(vnode)}if (__COMPAT__) {convertLegacyVModelProps(vnode)defineLegacyVNodeProperties(vnode)}return vnode
}
- 进入这个函数
type
,props
,children
,patchFlag
,dynamicProps
,shapeFlag
,isBlockNode
,needFullChildrenNormalization
- 接下来,创建 vnode对象,包含
__v_isVNode
- 这时候构建出了一个初始的vnode对象
- 初始化很多属性,我们只需要关注对我们有用的
- 继续执行,到
normalizeChildren(vnode, children)
- 这个函数里面涉及到一个 进位符 & 和 按位或赋值
|=
|=
这里是按位或运算- 这里展开下:
- 10进制的1转换成二进制是: 01,
- 10(2) === 2(10) 括号里面是进制
- 在vue的运算里,其实他们都是32位的
- 32位是指有32个比特位
- 00000000 00000000 00000000 00000000
- 二进制的1是:
- 00000000 00000000 00000000 00000001
- 当前调试debug的flag的值,10进制是1,也是如上表示
- 二进制的8是:
- 00000000 00000000 00000000 00001000
- 上述1和8执行或运算(有一个1则是1),得到
- 00000000 00000000 00000000 00001001
- 这个函数里面涉及到一个 进位符 & 和 按位或赋值
总结下
- h函数本质上是处理一个参数的问题
- 核心代码是在
_createVNode
中进行的 - 里面生成vnode的核心方法,做了一件重要的事情是构建了一个
shapeFlag
- 第一次构建的时候,它的flag是
ELEMENT
类型 - 接下来return 了
createBaseVNode
函数 - 它根据
type
,props
,children
,shapeFlag
生成了一个 vnode 节点 - 通过按位或运算,来改变flag的值,重新赋值给
shapeFlag
- 最终 return vnode 对象
这篇关于Vue3源码梳理:运行时之基于h函数生成vnode的内部流程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!