Vue.js使用createElement方法创建VNode
定义在 src/core/vdom/create-element.js 中
// wrapper function for providing a more flexible interface// without getting yelled at by flowexport 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方法的封装,它允许传入的参数更加灵活,在处理这些参数之后调用真正创建VNode的函数_createElement
export function _createElement (context: Component, // 上下文环境tag?: string | Class<Component> | Function | Object, // 标签data?: VNodeData, // VNode数据children?: any, // 当前VNode的子节点 需规范为标准的VNode数组normalizationType?: number // 子节点规范的类型 参考render函数是编译生成还是用户手写): 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()}}
children 的规范化
由于Virtual DOM实际上是一个树状结构,每一个VNode可能会有若干个子节点,这些子节点应该也是VNode类型
_createElement接收的第4个参数children是任意类型的,因此需要把它们规范成VNode类型
根据normalizationType的不同,调用了normalizeChildren(children)和simpleNormalizeChildren(children)方法
定义在 src/core/vdom/helpers/normalzie-children.js 中
// The template compiler attempts to minimize the need for normalization by// statically analyzing the template at compile time.//// For plain HTML markup, normalization can be completely skipped because the// generated render function is guaranteed to return Array<VNode>. There are// two cases where extra normalization is needed:// 1. When the children contains components - because a functional component// may return an Array instead of a single root. In this case, just a simple// normalization is needed - if any child is an Array, we flatten the whole// thing with Array.prototype.concat. It is guaranteed to be only 1-level deep// because functional components already normalize their own children.// simpleNormalizeChildren方法调用场景是 render函数是编译生成的// 理论上编译生成的children都已经是VNode类型的// 但是functional component函数式组件返回的是一个数组而不是一个根节点,所以会通过Array.prototype.concat方法把整个children数组打平,让它的深度只有一层export function simpleNormalizeChildren (children: any) {for (let i = 0; i < children.length; i++) {if (Array.isArray(children[i])) {return Array.prototype.concat.apply([], children)}}return children}// 2. When the children contains constructs that always generated nested Arrays,// e.g. <template>, <slot>, v-for, or when the children is provided by user// with hand-written render functions / JSX. In such cases a full normalization// is needed to cater to all possible types of children values.// normalizeChildren方法调用场景:// 一个是render函数是用户手写的,当children只有一个节点时Vue.js从接口层面允许用户把children写出基础类型用来创建单个简单的文本节点,这种情况会调用createTextVNode创建一个文本节点的VNode// 另一个是当编译slot、v-for的时候会产生嵌套数组的情况,会调用normalizeArrayChildren方法export function normalizeChildren (children: any): ?Array<VNode> {return isPrimitive(children)? [createTextVNode(children)]: Array.isArray(children)? normalizeArrayChildren(children): undefined}
normalizeArrayChildren
function normalizeArrayChildren (children: any, // 要规范的子节点nestedIndex?: string // 嵌套的索引): Array<VNode> {const res = []let i, c, lastIndex, last// 遍历所有的childrenfor (i = 0; i < children.length; i++) {// 单个节点c = children[i]// undefined || boolean类型直接退出此次循环if (isUndef(c) || typeof c === 'boolean') continuelastIndex = res.length - 1last = res[lastIndex]// nested// 数组类型if (Array.isArray(c)) {if (c.length > 0) {// 递归调用normalizeArrayChildrenc = 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 strings// 通过createTextVNode方法转换成VNode类型res[lastIndex] = createTextVNode(last.text + c)} else if (c !== '') {// convert primitive to vnoderes.push(createTextVNode(c))}} else {// 如果存在两个连续的text节点会把它们合并成一个text节点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)// 如果children是一个列表并且列表还存在嵌套情况,则根据nestedIndex去更新它的keyif (isTrue(children._isVList) &&isDef(c.tag) &&isUndef(c.key) &&isDef(nestedIndex)) {c.key = `__vlist${nestedIndex}_${i}__`}res.push(c)}}}return res}
children表示要规范的子节点,nestedIndex表示嵌套的索引,因为单个child可能是一个数组类型
经过对children的规范化,children变成了一个类型为VNode的Array
VNode 的创建
在_createElement函数中规范化children后回去创建一个VNode实例
export function _createElement (context: Component, // 上下文环境tag?: string | Class<Component> | Function | Object, // 标签data?: VNodeData, // VNode数据children?: any, // 当前VNode的子节点 需规范为标准的VNode数组normalizationType?: number // 子节点规范的类型 参考render函数是编译生成还是用户手写): 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)}// 创建VNodelet vnode, ns// 如果tag是字符串类型if (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)}// 直接创建一个普通VNodevnode = new VNode(config.parsePlatformTagName(tag), data, children,undefined, undefined, context)} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { // 如果是已注册的组件名// component// 通过createComponent创建一个组件类型的VNodevnode = 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 children// 创建一个未知的标签的VNodevnode = new VNode(tag, data, children,undefined, undefined, context)}} else { // 如果tag是component类型// direct component options / constructor// 直接调用createComponent创建一个组件类型的VNode节点vnode = 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()}}
