网站管理公司,爱网站站长工具,网站建设选择什么系统好,wordpress主题模板修改教程createElement
逻辑#xff1a;回到mountComponent函数的过程#xff0c;至此已经知道vm._render是如何创建了一个VNode#xff0c;接下来就是要把这个 VNode 渲染成一个真实的DOM并渲染出来#xff0c;这个过程是通过vm._update完成的#xff0c;接下来分析一下这个过程…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的定义支持六个参数第一个参数context是vm实例第二个tag是VNode标签tag: div第三个data是跟VNode相关的数据第四个children是VNode的子节点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 | ArrayVNode {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函数是编译生成的还是用户手写的。
_createElement对data进行校验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
}判断data和data.is如果component :is不是一个真值也是返回一个注释节点。
对data参数例如key不是基础类型则报错。
children的规范化
对children做normalizeChildren。当手写render函数时对第三个参数传了this.message那是一个普通的值但是实际上children应该是个数组而且每个数组都是VNode。normalizeChildren和simpleNormalizeChildren函数来自src/core/vdom/helpers/normalize-children.js文件。
1simpleNormalizeChildren
simpleNormalizeChildren对children进行了一层遍历。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]2normalizeChildren
normalizeChildren最终目标也是返回一个一维的数组每个都是VNode。
首先判断是否是个基础类型是的话直接返回一维数组数组长度为1createTextVNode实例化一个VNode前三个参数是undefined第四个参数是个string是返回一个文本VNode不是基础类型则判断是否是Array类型是的话调用normalizeArrayChildren方法否则返回undefined。
export function normalizeChildren (children: any): ?ArrayVNode {return isPrimitive(children)? [createTextVNode(children)]: Array.isArray(children)? normalizeArrayChildren(children): undefined
}3normalizeArrayChildren
normalizeArrayChildren返回的是res数组。遍历children如果children[i]是Array数组可能多层嵌套例如编译slot、v-for的时候会产生嵌套数组的情况递归调用normalizeArrayChildren做个优化如果最后一个节点和下次第一个节点都是文本则把这两个合并在一起再做一层push如果是基础类型判断是否是文本节点是的话则通过createTextVNode方法转换成VNode类型不是的话直接push如果是VNode例如v-for如果children是一个列表并且列表还存在嵌套的情况则根据nestedIndex去更新它的key。最终返回res。 normalizeArrayChildren主要是递归和合并。经过对children的规范化children变成了一个类型为VNode的Array。
function normalizeArrayChildren (children: any, nestedIndex?: string): ArrayVNode {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 原生保留标签。如果是则创建一个普通的保留标签然后直接创建一个普通vnode。vnode render.call(vm._renderProxy, vm.$createElement)函数返回的vnode是createElement(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 | ClassComponent | Function | Object,data?: VNodeData,children?: any,normalizationType?: number
): VNode | ArrayVNode {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()}
}