Vue3源码梳理:运行时之基于h函数生成vnode的内部流程
2023-12-17 20:50:17
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 } = Vue
const 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.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)
}
}
h 函数需要三个参数: type
, propsOrChildren
, children
- 注意第二个参数,
propsOrChildren
是一个对象,它可以是props
也可以是children
- 内部是基于传入的长度和类型来判断的,先长度(先基于2来判断的)后类型
- 最终返回
createVNode
- h函数本身只是对用户传递的参数的处理,其本质是
createVNode
- 使得
createVNode
调用时,更加的方便
3 ) 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 {
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 it
const 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.BAIL
return cloned
}
// class component normalization.
if (isClassComponent(type)) {
type = type.__vccOpts
}
// 2.x async/functional component compat
if (__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 } = 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
)
}
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 VNode
if (needFullChildrenNormalization) {
normalizeChildren(vnode, children)
// normalize suspense children
if (__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 key
if (__DEV__ && vnode.key !== vnode.key) {
warn(`VNode created with invalid key (NaN). VNode type:`, vnode.type)
}
// track vnode for block tree
if (
isBlockTreeEnabled > 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.
(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 对象
文章来源:https://blog.csdn.net/Tyro_java/article/details/135048772
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!