Vue3源码梳理:运行时之基于h函数生成vnode的内部流程

本文主要是介绍Vue3源码梳理:运行时之基于h函数生成vnode的内部流程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

VNode 节点类型

  • 对于vnode而言,具备很多节点类型
  • vue源码中patch函数switch处理包含了好几种类型,常见类型如下
    • Text:文本节点
    • Comment:注释节点
    • Static:静态dom节点
    • Fragment:包含多个根节点的模板被表示为一个片段 fragment
    • ELEMENT: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, childrenshapeFlag 生成了一个 vnode 节点
  • 通过按位或运算,来改变flag的值,重新赋值给 shapeFlag
  • 最终 return vnode 对象

这篇关于Vue3源码梳理:运行时之基于h函数生成vnode的内部流程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/507724

相关文章

全面掌握 SQL 中的 DATEDIFF函数及用法最佳实践

《全面掌握SQL中的DATEDIFF函数及用法最佳实践》本文解析DATEDIFF在不同数据库中的差异,强调其边界计算原理,探讨应用场景及陷阱,推荐根据需求选择TIMESTAMPDIFF或inte... 目录1. 核心概念:DATEDIFF 究竟在计算什么?2. 主流数据库中的 DATEDIFF 实现2.1

MySQL中的LENGTH()函数用法详解与实例分析

《MySQL中的LENGTH()函数用法详解与实例分析》MySQLLENGTH()函数用于计算字符串的字节长度,区别于CHAR_LENGTH()的字符长度,适用于多字节字符集(如UTF-8)的数据验证... 目录1. LENGTH()函数的基本语法2. LENGTH()函数的返回值2.1 示例1:计算字符串

linux解压缩 xxx.jar文件进行内部操作过程

《linux解压缩xxx.jar文件进行内部操作过程》:本文主要介绍linux解压缩xxx.jar文件进行内部操作,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、解压文件二、压缩文件总结一、解压文件1、把 xxx.jar 文件放在服务器上,并进入当前目录#

MySQL 中的 CAST 函数详解及常见用法

《MySQL中的CAST函数详解及常见用法》CAST函数是MySQL中用于数据类型转换的重要函数,它允许你将一个值从一种数据类型转换为另一种数据类型,本文给大家介绍MySQL中的CAST... 目录mysql 中的 CAST 函数详解一、基本语法二、支持的数据类型三、常见用法示例1. 字符串转数字2. 数字

Python内置函数之classmethod函数使用详解

《Python内置函数之classmethod函数使用详解》:本文主要介绍Python内置函数之classmethod函数使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录1. 类方法定义与基本语法2. 类方法 vs 实例方法 vs 静态方法3. 核心特性与用法(1编程客

Python函数作用域示例详解

《Python函数作用域示例详解》本文介绍了Python中的LEGB作用域规则,详细解析了变量查找的四个层级,通过具体代码示例,展示了各层级的变量访问规则和特性,对python函数作用域相关知识感兴趣... 目录一、LEGB 规则二、作用域实例2.1 局部作用域(Local)2.2 闭包作用域(Enclos

MySQL count()聚合函数详解

《MySQLcount()聚合函数详解》MySQL中的COUNT()函数,它是SQL中最常用的聚合函数之一,用于计算表中符合特定条件的行数,本文给大家介绍MySQLcount()聚合函数,感兴趣的朋... 目录核心功能语法形式重要特性与行为如何选择使用哪种形式?总结深入剖析一下 mysql 中的 COUNT

Spring Security中用户名和密码的验证完整流程

《SpringSecurity中用户名和密码的验证完整流程》本文给大家介绍SpringSecurity中用户名和密码的验证完整流程,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定... 首先创建了一个UsernamePasswordAuthenticationTChina编程oken对象,这是S

MySQL 中 ROW_NUMBER() 函数最佳实践

《MySQL中ROW_NUMBER()函数最佳实践》MySQL中ROW_NUMBER()函数,作为窗口函数为每行分配唯一连续序号,区别于RANK()和DENSE_RANK(),特别适合分页、去重... 目录mysql 中 ROW_NUMBER() 函数详解一、基础语法二、核心特点三、典型应用场景1. 数据分

MySQL数据库的内嵌函数和联合查询实例代码

《MySQL数据库的内嵌函数和联合查询实例代码》联合查询是一种将多个查询结果组合在一起的方法,通常使用UNION、UNIONALL、INTERSECT和EXCEPT关键字,下面:本文主要介绍MyS... 目录一.数据库的内嵌函数1.1聚合函数COUNT([DISTINCT] expr)SUM([DISTIN