createElement

createElement

逻辑:回到mountComponent函数的过程,至此已经知道vm._render是如何创建了一个VNode,接下来就是要把这个 VNode 渲染成一个真实的DOM并渲染出来,这个过程是通过vm._update完成的,接下来分析一下这个过程。

Vue.js 利用createElement方法创建VNode,它定义在src/core/vdom/create-element.js中:

src/core/vdom/create-element.js文件解析

createElement()

createElement的定义支持六个参数,第一个参数contextvm实例,第二个tagVNode标签(tag: "div"),第三个data是跟VNode相关的数据,第四个childrenVNode的子节点(children: [VNode]),有children才能构造成VNode tree,可以完美映射到DOM Tree

进行参数重载,检测参数,是对参数个数不一致的处理。即没有data,传入的是children,就会把参数往后移动。

alwaysNormalize进行判断,然后为normalizationType赋值常变量。

createElement方法实际上是对_createElement方法的封装,它允许传入的参数更加灵活,在处理这些参数后,调用真正创建 VNode的函数_createElement

// src/core/vdom/create-element.js
export function createElement (context: Component,tag: any,data: any,children: any,normalizationType: any,alwaysNormalize: boolean
): VNode | Array<VNode> {if (Array.isArray(data) || isPrimitive(data)) {normalizationType = childrenchildren = datadata = undefined}if (isTrue(alwaysNormalize)) {normalizationType = ALWAYS_NORMALIZE}return _createElement(context, tag, data, children, normalizationType)
}

_createElement()

_createElement函数的流程略微有点多,我们接下来主要分析 2 个重点的流程 —— children的规范化以及VNode的创建。

_createElement方法有5个参数,context表示VNode的上下文环境,它是Component类型;tag表示标签,它可以是一个字符串,也可以是一个Componentdata表示VNode的数据,它是一个VNodeData类型,可以在flow/vnode.js中找到它的定义,这里先不展开说;children表示当前VNode的子节点,它是任意类型的,它接下来需要被规范为标准的VNode数组normalizationType表示子节点规范的类型,类型不同规范的方法也就不一样,它主要是参考render函数是编译生成的还是用户手写的。

_createElementdata进行校验,data不能是响应式的(有__ob__属性代表是响应式的),否则报警告“ VNode data 不能是响应式的 ”。然后调用createEmptyVNode函数。

createEmptyVNode方法定义在src/core/vdom/vnode.js文件中,即简单创建VNode实例,什么参数都不传,可以理解为是一个注释节点。

// src/core/vdom/vnode.js
export const createEmptyVNode = (text: string = '') => {const node = new VNode()node.text = textnode.isComment = truereturn node
}

判断datadata.is,如果component :is不是一个真值,也是返回一个注释节点。

data参数,例如key不是基础类型则报错。

children的规范化

childrennormalizeChildren。当手写render函数时,对第三个参数传了this.message,那是一个普通的值,但是实际上children应该是个数组,而且每个数组都是VNodenormalizeChildrensimpleNormalizeChildren函数来自src/core/vdom/helpers/normalize-children.js文件。

(1)simpleNormalizeChildren

simpleNormalizeChildrenchildren进行了一层遍历。children是个类数组,遍历发现如果有元素是数组,就调用Array.prototype.concat.apply()方法把children拍平(只拍一次),就是让嵌套数组成为一维数组(是因为存在functional component函数式组件返回的是一个数组而不是一个根节点)。最终的期望就是children是个一维数组,每个都是一个VNode

  • Array.isArray():判断传递的值是否是一个 Array 。如果对象是 Array ,则返回 true ,否则为 false 。

  • 数组降维

    let children = [1, 2, [3, [4, 5, 6], 7], 8, [9, 10]];
    function simpleNormalizeChildren(children) {return Array.prototype.concat.apply([], children);
    }
    console.log(simpleNormalizeChildren(children)) // [1, 2, 3, [4, 5, 6], 7, 8, 9, 10]
    

(2)normalizeChildren

normalizeChildren最终目标也是返回一个一维的数组,每个都是VNode

首先判断是否是个基础类型,是的话直接返回一维数组,数组长度为1,createTextVNode实例化一个VNode,前三个参数是undefined,第四个参数是个string,是返回一个文本VNode;不是基础类型则判断是否是Array类型,是的话调用normalizeArrayChildren方法,否则返回undefined

export function normalizeChildren (children: any): ?Array<VNode> {return isPrimitive(children)? [createTextVNode(children)]: Array.isArray(children)? normalizeArrayChildren(children): undefined
}

(3)normalizeArrayChildren

normalizeArrayChildren返回的是res数组。遍历children,如果children[i]Array数组(可能多层嵌套,例如编译slotv-for的时候会产生嵌套数组的情况),递归调用normalizeArrayChildren,做个优化(如果最后一个节点和下次第一个节点都是文本,则把这两个合并在一起),再做一层push;如果是基础类型,判断是否是文本节点,是的话则通过createTextVNode方法转换成VNode类型,不是的话直接push;如果是VNode,例如v-for,如果children是一个列表并且列表还存在嵌套的情况,则根据nestedIndex去更新它的key。最终返回res
normalizeArrayChildren主要是递归和合并。经过对children的规范化,children变成了一个类型为VNodeArray

function normalizeArrayChildren (children: any, nestedIndex?: string): Array<VNode> {const res = []let i, c, lastIndex, lastfor (i = 0; i < children.length; i++) {c = children[i]if (isUndef(c) || typeof c === 'boolean') continuelastIndex = res.length - 1last = res[lastIndex]//  nestedif (Array.isArray(c)) {if (c.length > 0) {c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`)// merge adjacent text nodesif (isTextNode(c[0]) && isTextNode(last)) {res[lastIndex] = createTextVNode(last.text + (c[0]: any).text)c.shift()}res.push.apply(res, c)}} else if (isPrimitive(c)) {if (isTextNode(last)) {// merge adjacent text nodes// this is necessary for SSR hydration because text nodes are// essentially merged when rendered to HTML stringsres[lastIndex] = createTextVNode(last.text + c)} else if (c !== '') {// convert primitive to vnoderes.push(createTextVNode(c))}} else {if (isTextNode(c) && isTextNode(last)) {// merge adjacent text nodesres[lastIndex] = createTextVNode(last.text + c.text)} else {// default key for nested array children (likely generated by v-for)if (isTrue(children._isVList) &&isDef(c.tag) &&isUndef(c.key) &&isDef(nestedIndex)) {c.key = `__vlist${nestedIndex}_${i}__`}res.push(c)}}}return res
}
VNode 的创建

tag进行判断,是个string还是组件。如果是string,判断是不是 HTML 原生保留标签。如果是则创建一个普通的保留标签,然后直接创建一个普通vnodevnode = render.call(vm._renderProxy, vm.$createElement)函数返回的vnodecreateElement(vm, a, b, c, d, true)的返回值。同时把vnode返回给Vue.prototype._render

这里先对tag做判断,如果是string类型,则接着判断是不是 HTML 原生保留标签,则直接创建一个普通vnode,如果是为已注册的组件名,则通过createComponent创建一个组件类型的vnode,否则创建一个未知的标签的vnode。如果是tag一个Component类型,则直接调用createComponent创建一个组件类型的vnode节点。对于createComponent创建组件类型的vnode的过程,本质上它还是返回了一个vnode

// src/core/vdom/create-element.js
export function _createElement (context: Component,tag?: string | Class<Component> | Function | Object,data?: VNodeData,children?: any,normalizationType?: number
): VNode | Array<VNode> {if (isDef(data) && isDef((data: any).__ob__)) {process.env.NODE_ENV !== 'production' && warn(`Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +'Always create fresh vnode data objects in each render!',context)return createEmptyVNode()}// object syntax in v-bindif (isDef(data) && isDef(data.is)) {tag = data.is}if (!tag) {// in case of component :is set to falsy valuereturn createEmptyVNode()}// warn against non-primitive keyif (process.env.NODE_ENV !== 'production' &&isDef(data) && isDef(data.key) && !isPrimitive(data.key)) {if (!__WEEX__ || !('@binding' in data.key)) {warn('Avoid using non-primitive value as key, ' +'use string/number value instead.',context)}}// support single function children as default scoped slotif (Array.isArray(children) &&typeof children[0] === 'function') {data = data || {}data.scopedSlots = { default: children[0] }children.length = 0}if (normalizationType === ALWAYS_NORMALIZE) {children = normalizeChildren(children)} else if (normalizationType === SIMPLE_NORMALIZE) {children = simpleNormalizeChildren(children)}let vnode, nsif (typeof tag === 'string') {let Ctorns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)if (config.isReservedTag(tag)) {// platform built-in elementsif (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn) && data.tag !== 'component') {warn(`The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,context)}vnode = new VNode(config.parsePlatformTagName(tag), data, children,undefined, undefined, context)} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {// componentvnode = createComponent(Ctor, data, context, children, tag)} else {// unknown or unlisted namespaced elements// check at runtime because it may get assigned a namespace when its// parent normalizes childrenvnode = new VNode(tag, data, children,undefined, undefined, context)}} else {// direct component options / constructorvnode = createComponent(tag, data, context, children)}if (Array.isArray(vnode)) {return vnode} else if (isDef(vnode)) {if (isDef(ns)) applyNS(vnode, ns)if (isDef(data)) registerDeepBindings(data)return vnode} else {return createEmptyVNode()}
}

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

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

相关文章

共筑关基安全防线,开源网安加入中关村华安关键信息基础设施安全保护联盟

近日&#xff0c;开源网安正式加入“中关村华安关键信息基础设施安全保护联盟”&#xff08;以下简称&#xff1a;关保联盟&#xff09;成为会员单位&#xff0c;进一步加强与行业内重要机构、企业的协同合作&#xff0c;推动关键信息基础设施安全保护领域的生态建设。 未来&am…

主机怎么通过命令行方式向虚拟机传输文件

这是几个月前遇到的问题了&#xff0c;那时候想着要记录下来&#xff0c;但后来忙忘了&#xff0c;这次想起来了&#xff0c;于是记录一下。 之前打靶场的时候需要将netcat-win32-1.12放入虚拟机的/var/www/html下&#xff0c;但是我虚拟机无法上网&#xff0c;也就是说无法直…

[每周一更]-(第74期):Docker-compose 部署Jenkins容器-英文版及错误纠错

1、前文概要 通过物理机部署Jenkins前文已经讲过&#xff08;地址&#xff1a;[Jenkins] 物理机 安装 Jenkins&#xff09;&#xff0c;也已经公司内部平稳运行若干年&#xff0c;考虑到容器化的使用场景&#xff0c;部分项目都采用容器运行&#xff0c;开始考虑部署容器化的J…

RK3568 android11 实现双路I2C触摸 --GT9xx

一&#xff0c;GT911 触摸屏简介 它的接口类型为 I2C &#xff0c;供电电压和通讯电压均为 3.3V 。这款电容触摸屏内置了上拉电阻&#xff0c;这意味着我们的开发板上与该触摸屏的接口处不需要设置上拉电阻。关于线序&#xff0c;同样是 GT911 &#xff0c;不同批次的器件都有…

U-Net及其变体在医学图像分割中的应用研究综述

U-Net及其变体在医学图像分割中的应用研究综述 论文来自&#xff1a;中国生物医学工程学报 2022 摘 要&#xff1a; 医学图像分割可以为临床诊疗和病理学研究提供可靠的依据&#xff0c;并能辅助医生对病人的病情做出准确的判断。 基于深度学习的分割网络的出现解决了传统自动分…

数据结构与算法(三)贪心算法(Java)

目录 一、简介1.1 定义1.2 基本步骤1.3 优缺点 二、经典示例2.1 选择排序2.2 背包问题 三、经典反例&#xff1a;找零钱3.1 题目3.2 解答3.3 记忆化搜索实现3.4 动态规划实现 一、简介 1.1 定义 贪心算法&#xff08;Greedy Algorithm&#xff09;&#xff0c;又名贪婪法&…

串口波形延时问题再次故障测试分析

先放电路图吧 这个延时问题测试了很多天&#xff0c;怎么感觉总是有没有想到的问题可以测试&#xff0c;总是有原件可以替换改善问题&#xff0c;再次测试了三极管的C脚波形&#xff1a; 从上到下的3个波形分别是MCU出来的波形&#xff0c;经过三极管反向的波形&#xff0c;…

2.ORB-SLAM3中如何从二进制文件中加载多地图、关键帧、地图点等数据结构

目录 1 为什么保存&加载(视觉)地图 1.1 加载多地图的主函数 1.2 加载各个地图 Atlas::PostLoad 1.3 加载关键帧及地图点Map::PostLoad 1.4 恢复地图点信息 MapPoint::PostLoad 1.5 恢复关键帧信息KeyFrame::PostLoad 1 为什么保存&加载(视觉)地图 因为我们要去做导…

容器虚拟化原理

目录 容器简介容器技术优缺点容器的组成基本技术namespace创建namespace的三种方法PID namespaceIPC namespacemount namespacenetwork namespaceUTC namespace cgroup(control group)rootfs镜像分层 容器创建原理 容器简介 容器是一种打包应用及其运行环境的方式&#xff0c;…

抽象类的使用—模板设计模式 Java

模板设计模式 一、引入二、改进 一、引入 需求 ① 有多个类&#xff0c;完成不同的任务 job ② 要求统计得到各自完成任务的时间 ③ 请编程实现 >最容易想到的方法&#xff0c;写类&#xff0c;统计时间 AA BB中的 job 方法中是有重复的。 >改进1&#xff1a;每个类中&…

SpringBoot整合EasyExcel实现复杂Excel表格的导入导出功能

文章目录 &#x1f389;SpringBoot整合EasyExcel实现复杂Excel表格的导入&导出功能 ☆* o(≧▽≦)o *☆嗨~我是IT陈寒&#x1f379;✨博客主页&#xff1a;IT陈寒的博客&#x1f388;该系列文章专栏&#xff1a;架构设计&#x1f4dc;其他专栏&#xff1a;Java学习路线 Jav…

集成IDE开发环境,Java开发工具IntelliJ IDEA 2023中文

IntelliJ IDEA 2023是一款功能强大的软件&#xff0c;其为程序员提供了一款先进的集成开发环境。它以智能、高效和人性化为主要特点&#xff0c;致力于提高开发人员的生产力&#xff0c;帮助程序员更快、更好地编写代码。IntelliJ IDEA 2023支持多种语言和框架&#xff0c;包括…

继承JsonSerializer+注解实现自定义数据脱敏方案

1、数据脱敏 数据脱敏是一种保护隐私数据的技术&#xff0c;通过将敏感信息转化为非敏感信息来实现对数据的保护&#xff0c;以保护敏感隐私数据的可靠性和安全性。 数据脱敏可以分为可恢复和不可恢复两类: 可恢复类可以通过一定的方式恢复成原来的敏感数据。不可恢复类则无…

村村通工程(Kruskal算法)/最小生成树Kruskal算法【数据结构】

村村通工程&#xff08;Kruskal算法&#xff09; 题目描述 "村村通"是国家一个系统工程&#xff0c;其包涵有&#xff1a;公路、电力、生活和饮用水、电话网、有线电视网、互联网等等。 村村通公路工程&#xff0c;是国家为构建和谐社会&#xff0c;支持新农村建设…

【Shell】Shell基础学习

一、shell脚本 (1)第一个shell脚本 #!/bin/bash #this is a comment echo "hello world"一个shell脚本永远以“#!”开头,这是一个脚本开始的标记,它是告诉系统执行这个文件需要用某个解释器,后面的/bin/bash就是指明解释器的具体位置。 “#”开头是注释 …

P8A003-系统加固-系统管理员账户安全

【预备知识】 Administrator 原意为管理人或行政官员或遗产管理人&#xff0c;在计算机名词中&#xff0c;它的意思是系统超级管理员或超级用户。但是在Windows系统中此用户名只在安全模式中使用。 【实验步骤】 网络拓扑&#xff1a;server2008-basic windows server 2008 …

LeetCode [简单]118. 杨辉三角

给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 public class Solution {public IList<IList<int>> Generate(int numRows) {List<IList<int>> res new …

交流负载测试使用场景

交流负载测试是一种在特定环境下&#xff0c;对电力设备、汽车电子部件&#xff0c;工业自动化设备、网络设备、家电产品&#xff0c;航空航天设备以及医疗器械等产品进行测试的方法&#xff0c;该测试的目的是评估这些设备在实际运行条件下的性能和可靠性。 1电力设备测试 交…

Zabbix 6.0 详细基础介绍

目录 一、如何选择自己的运维监控平台 1.1 常用的开源运维监控工具 1.1.1 Cacti 1.1.2 Nagios 1.1.3 Ganglia 1.1.4 Centreon 1.1.5 Grafana 1.1.6 Prometheus 1.1.7 Zabbix 1.2 监控工具选型经验 1.2.1 中小企业传统监控选择 Zabbix 1.2.2 云原生监控选择 Prometh…

虚幻学习笔记2—点击场景3D物体的两种处理方式

一、前言 本文使用的虚幻引擎为5.3.2&#xff0c;两种方式分别为&#xff1a;点击根物体和精准点击目标物体。 二、实现 2.1、玩家控制器中勾选鼠标点击事件&#xff1a;这一步很重要&#xff0c;如图2.1.1所示&#xff1a;在自定义玩家控制器中勾 图2.1.1 选该项&#xff0c…