vue原始碼學習-createElement
createElement
export function createElement ( context: Component, // 傳入的vm例項 tag: any, // tag標籤 data: any, // 跟vnode相關的資料 children: any, // vnode的子節點 normalizationType: any, // alwaysNormalize: boolean ): VNode | Array<VNode> { if (Array.isArray(data) || isPrimitive(data)) { normalizationType = children children = data data = undefined } if (isTrue(alwaysNormalize)) { normalizationType = ALWAYS_NORMALIZE } return _createElement(context, tag, data, children, normalizationType) }
在createElement
函式中,首先堅持data的型別,通過判斷data是不是陣列,以及是不是基本型別,來判斷data
是否傳入。如果沒有傳入,則將所有的引數向前賦值,且data=undefined
。
然後,判斷傳入的alwaysNormalize
引數是否為真,為真的話,normalizationType
賦值為ALWAYS_NORMALIZE
。
最後,在呼叫_createElement
函式,可以看到,createElement
是對引數做了一些處理以後,將處理好的引數傳給_createElement
函式。
_createElement
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-bind if (isDef(data) && isDef(data.is)) { tag = data.is } if (!tag) { // in case of component :is set to falsy value return createEmptyVNode() } // warn against non-primitive key if (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 slot if (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, ns if (typeof tag === 'string') { let Ctor ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) if (config.isReservedTag(tag)) { // platform built-in elements if (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))) { // component vnode = 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 vnode = new VNode( tag, data, children, undefined, undefined, context ) } } else { // direct component options / constructor 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() } }
在_createElement
函式中,首先判斷data
是不是響應式的,vnode
中的data不能是響應式的。如果是,則Vue丟擲警告。
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() }
最重要的部分死將children拍平為單維陣列。在simpleNormalizeChildren
函式中,主要完成的功能是將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
}
在normalizeChildren
函式中,主要呼叫normalizeArrayChildren
函式處理類陣列
export function normalizeChildren (children: any): ?Array<VNode> {
return isPrimitive(children)
? [createTextVNode(children)]
: Array.isArray(children)
? normalizeArrayChildren(children)
: undefined
}
normalizeArrayChildren
在normalizeArrayChildren
函式中,主要完成的功能是判斷children
中的元素是不是陣列,如果是的話,就遞迴呼叫陣列,並將每個元素儲存在陣列中返回。
首先判斷children
中的元素是不是陣列,是的話遞迴呼叫函式。如果第一個和最後一個都是文字節點的話,將其合併,優化
然後判斷該元素是不是基本型別。如果是,在判斷最後一個節點是不是文字節點,是的話將其與鈣元素合併為一個文字接地那。否則,把這個基本型別轉換為文字節點(VNode)。最後一種情況,該元素是一個VNode,先同樣進行優化(合併第一個和最後一個節點),最後判斷該節點的屬性,最後將該節點加入到結果中。
function normalizeArrayChildren (children: any, nestedIndex?: string): Array<VNode> {
const res = []
let i, c, lastIndex, last
for (i = 0; i < children.length; i++) {
c = children[i]
if (isUndef(c) || typeof c === 'boolean') continue
lastIndex = res.length - 1
last = res[lastIndex]
// nested
if (Array.isArray(c)) {
if (c.length > 0) {
c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`)
// merge adjacent text nodes
if (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
res[lastIndex] = createTextVNode(last.text + c)
} else if (c !== '') {
// convert primitive to vnode
res.push(createTextVNode(c))
}
} else {
if (isTextNode(c) && isTextNode(last)) {
// merge adjacent text nodes
res[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
}
回到_createElement
函式中,在講children
拍平為一維陣列後,接著判斷標籤(tag)是不是字串,是的話,則判斷該標籤是不是平臺內建的標籤(如:div
),是的話則建立該VNode。
然後判斷該標籤是不是元件,是的話,建立一個元件VNode
如果都不是,則按照該標籤名建立一個VNode
至此,_createElement
函式已經完成建立VNode的功能,將其返回給render函式。
總結
createElement
函式主要是通過對children
進行扁平化處理後,通過標籤(tag)判斷,使用VNode類來生成vnode,並將其返回。