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 对象

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/230699.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SHT10温湿度传感器——STM32驱动

———————实验效果——————— &#x1f384;硬件外观 &#x1f384;接线 &#x1f388; 3.3V供电 &#x1f388; IIC通讯 &#x1f384; 代码获取 &#x1f388; 查看下方 ———————END———————

FB使用汇编模拟GoSub(子函数)功能

在FB里不支持GoSub功能&#xff0c;在面对函数内简单又重复的操作&#xff0c;而所涉及变量又比较多的时候&#xff0c;再在外边定义一个函数就显得累赘&#xff0c;此时如果可以有一个函数内部的子函数&#xff0c;就显得方便多了。 在汇编探索里发现&#xff0c;可以使用汇编…

20231218在微软官网下载WINDOWS10以及通过rufus-4.3p写入U盘作为安装盘

20231218在微软官网下载WINDOWS10以及通过rufus-4.3p写入U盘作为安装盘 2023/12/18 17:06 百度搜索&#xff1a;下载 windows10 https://www.microsoft.com/zh-cn/software-download/windows10 下载 Windows 10 更新之前&#xff0c;请参阅 Windows 版本信息状态中的已知问题&a…

企业要想成功就必须培养“支持说真话的文化”

大家好&#xff0c;欢迎来到我的博客。今天&#xff0c;我想和大家谈谈企业文化的重要性&#xff0c;特别是支持说真话的文化。 1. 为什么说真话很重要&#xff1f; 在当今社会&#xff0c;说真话似乎越来越难了。我们害怕得罪别人&#xff0c;害怕被孤立&#xff0c;害怕被认…

系统设计——系统安全

HTTPS 是如何工作的&#xff1f; 安全超文本传输​​协议&#xff08;HTTPS&#xff09;是超文本传输​​协议&#xff08;HTTP&#xff09;的扩展。HTTPS 使用传输层安全性&#xff08;TLS&#xff09;传输加密数据。如果数据在网上被劫持&#xff0c;劫持者得到的只是二进制…

php去掉数组的key,重组数组的方法

php去掉数组的key&#xff0c;重组数组的方法 方法一&#xff1a;foreach循环方法二&#xff1a;array_values()函数方法三&#xff1a;array_map()函数方法四&#xff1a;强制类型转换 方法一&#xff1a;foreach循环 使用foreach循环遍历数组时&#xff0c;可以只取出数组的…

TaxtArea中内嵌一张放松图片,该图片实现属性悬浮放大功能

TaxtArea中内嵌一张发送图片&#xff0c;该图片实现属性悬浮放大功能&#xff0c;离开还原效果&#xff0c;点击发送按钮后&#xff0c;发送图片变为loading&#xff0c; <div class"textarea-wrapper" ><a-textarearef"textArea"v-model.trim&q…

汇编语言学习(3)

更好的阅读体验&#xff0c;请点击 YinKai s Blog 。 内存段 ​ 上面讨论的汇编程序的三个部分&#xff0c;也代码各种内存段。 ​ 有趣的是&#xff0c;如果将 section 关键字替换为 segment&#xff0c;将会得到相同的结果&#xff0c;这是因为对于汇编器而言&#xff0c;这…

web应用开发技术的一些概念

一、Servlet 1.Servlet的工作过程&#xff1a; Servelt的工作流程示意图 &#xff08;1&#xff09;客户端发起一个Http请求到服务器&#xff0c;请求特定的资源或者是要执行特定的操作 &#xff08;2&#xff09;服务器在接收到请求后&#xff0c;根据请求相应的URL将请求分发…

PostgreSQL进阶操作

PostgreSQL进阶操作 SQL执行顺序 (9) SELECT (10) DISTINCT col1, [OVER()] (6) AGG_FUNC(col2) (1) FROM table1 (3) JOIN table2 (2) ON table1.col table2.col (4) WHERE constraint_expression (5) GROUP BY col (7) WITH CUBE|ROLLUP (8) HAVING constraint_expression…

21、同济、微软亚研院、西安电子科技大提出HPT:层次化提示调优,独属于提示学习的[安妮海瑟薇]

前言&#xff1a; 本论文由同济大学、微软亚洲研究院、西安电子科技大学&#xff0c;于2023年12月11日中了AAAI2024 论文&#xff1a; 《Learning Hierarchical Prompt with Structured Linguistic Knowledge for Vision-Language Models》 地址&#xff1a; [2312.06323]…

C++泛型超详细合集-泛化的编程方式-程序员编写一个函数/类的代码让编译器去填补出不同的函数实现-供大家学习研究参考

以Add函数为例&#xff0c;在函数模板存在的同时&#xff0c;我们还可以单独写一个int类型的add函数。这都归功于函数重载的存在。 同时&#xff0c;我们还可以使用<int>来指定函数模板重载为已存在的Add函数。因为本质上这两个函数是不同的&#xff0c;并不会冲突。 下…

js 数据类型

js的八种数据类型&#xff1a; 基本类型&#xff08;基本类型&#xff09;&#xff1a;Number&#xff0c;String&#xff0c;Boolean&#xff0c;Undefined&#xff0c;Null&#xff0c;Symbol 引用数据类型&#xff08;对象类型&#xff09;&#xff1a;Object&#xff0c;…

mybatis中xml文件容易搞混的属性

目录 第一章、1.1&#xff09;MyBatis中resultMap标签1.2&#xff09;MyBatis的resultType1.3&#xff09;MyBatis的parameterType1.4&#xff09;type属性1.5&#xff09;jdbcType属性1.6&#xff09;javaType属性1.7&#xff09;ofType属性 友情提醒: 先看文章目录&#xff…

【k8s】--insecure-registry详解 ( 访问仓库、https、http)

文章目录 一、--insecure-registry是什么二、如何使用--insecure-registry三、--insecure-registry的安全风险四、--insecure-registry的替代方案五、总结参考 一、–insecure-registry是什么 --insecure-registry是docker中用来设置与docker registry通信的安全限制的一个参数…

猫粮哪个牌子好又安全?好又安全的主食冻干猫粮牌子推荐

现在越来越多的铲屎官关注猫咪的食品选择&#xff0c;而冻干猫粮一直是热门话题。其中主食冻干的肉含量很高&#xff0c;富含猫咪成长所需的蛋白质、维生素等营养物质。而且冻干工艺还保留了食材的原始风味&#xff0c;复水后可以恢复鲜肉的口感&#xff0c;猫咪很喜欢吃&#…

人工智能Keras图像分类器(CNN卷积神经网络的图片识别篇)

上期文章我们分享了人工智能Keras图像分类器(CNN卷积神经网络的图片识别的训练模型),本期我们使用预训练模型对图片进行识别:Keras CNN卷积神经网络模型训练 导入第三方库 from keras.preprocessing.image import img_to_array from keras.models import load_model impor…

.net web API的文件传输(上传和下载)客户端winform

防止反复造轮子&#xff0c;直接上代码。 客户端代码如下&#xff1a; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.IO.Compression; using System.Linq; using …

conda channel的镜像设置

目录 前言1 显示所有channel2 移除清华镜像3 添加可用的清华源4 下载opencv5 一些其他的conda指令参考文献 ———————————————— 版权声明&#xff1a;本文为CSDN博主「宇内虹游」的原创文章&#xff0c;遵循CC 4.0 BY-SA版权协议&#xff0c;转载请附上原文出处链…

关于“Python”的核心知识点整理大全27

目录 10.5 小结 第&#xff11;1 章 测试代码 11.1 测试函数 name_function.py 函数get_formatted_name()将名和姓合并成姓名&#xff0c;在名和姓之间加上一个空格&#xff0c;并将它们的 首字母都大写&#xff0c;再返回结果。为核实get_formatted_name()像期望的那样工…