Vue version: 2.5.17-beta.0
<Vue source notes - data driven - render >It is recorded in, and the vnode data return is created by the vm.$createElement function. The following records the implementation process of creating vnode by the common node of vm.$createElement.
new Vue({ el: '#app', render: createElement => createElement('div', { attrs: { id: 'app' } }, 'Hello Vue!') })
The above example is to use vm.$createElement to create a normal node vnode and finally mount it to the dom. VM. $create element calls createElement method. CreateElement is in src/core/vdom/create-element.js file. The source code is as follows:
const SIMPLE_NORMALIZE = 1 const ALWAYS_NORMALIZE = 2 export function createElement ( context: Component, tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean ): VNode | Array<VNode> { // A treatment of the inconsistency of the number of parameters 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) }
It can be seen from the above source code that in the createElement function, the parameters are mainly processed one layer, and then the ﹐ createElement function will be called. The source code is as follows:
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 } // No tag returns a comment node if (!tag) { // in case of component :is set to falsy value return createEmptyVNode() } // warn against non-primitive key // key if it is non basic type alarm 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 } // Processing children into a one-dimensional array if (normalizationType === ALWAYS_NORMALIZE) { children = normalizeChildren(children) } else if (normalizationType === SIMPLE_NORMALIZE) { children = simpleNormalizeChildren(children) } let vnode, ns // tag: string/object // The tag is string. Now it is judged whether html is used to create a vnode directly. Instead of finding the component vnode from vm.$options through resolveAsset, and then creating a vnode that doesn't recognize the tag if it can't be found if (typeof tag === 'string') { let Ctor // namespace processing ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) // Determine whether the tag is a html native reserved tag if (config.isReservedTag(tag)) { // platform built-in elements // config.parsePlatformTagName(tag): create platform reservation tag vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ) } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { // Component parsing // 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 // Unknown tag creates vnode vnode = new VNode( tag, data, children, undefined, undefined, context ) } } else { // When a component is passed in when render: H = > H (APP), tag follows this logic for the object // 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() } }
There are two main concerns in the above source code, one is to process children, the other is to generate vnode data.
children are processed in the following code:
if (normalizationType === ALWAYS_NORMALIZE) { children = normalizeChildren(children) } else if (normalizationType === SIMPLE_NORMALIZE) { children = simpleNormalizeChildren(children) }
It can be seen from the source code that different methods are called according to the normalizationType to handle children, while the normalizationType is brought from the createElement. The following are the source codes of normalizeChildren and simpleNormalizeChildren:
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 } export function normalizeChildren (children: any): ?Array<VNode> { return isPrimitive(children) ? [createTextVNode(children)] : Array.isArray(children) ? normalizeArrayChildren(children) : undefined } function isTextNode (node): boolean { return isDef(node) && isDef(node.text) && isFalse(node.isComment) } 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)) { // The current child node calls normalizeArrayChildren recursively for arrays if (c.length > 0) { c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`) // merge adjacent text nodes // The current sub node and the next sub node to be processed are both text nodes, and then they are combined into one 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)) { // Determine whether the next processing node is text based on the current child node // If it's a combination, it's a push 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 { // The current child node is a vnode node // Determine whether the child node is a text node and whether the next processing node is a text node // If it's a combination, it's a push 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 }
As can be seen from the above source code, the simpleNormalizeChildren function is relatively simple. To judge whether there is an array in children's children, you can flatten children into one-dimensional arrays and return them.
The normalizeChildren function is relatively complex. If the incoming children are of the original type, a text vnode is created to return. If it is an array, the normalizearaychildren method is called. The main task of the normalizeChildren function is to create a text vnode from a text node that does not generate vnode data in children.
The vnode data processing is generated in the following code:
if (typeof tag === 'string') { let Ctor // namespace processing ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) // Determine whether the tag is a html native reserved tag if (config.isReservedTag(tag)) { // platform built-in elements // config.parsePlatformTagName(tag): create platform reservation tag vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ) } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { // Component parsing // 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 // Unknown tag creates vnode vnode = new VNode( tag, data, children, undefined, undefined, context ) } }
As can be seen from the above source code, first of all, it determines whether the incoming tag is of string type. If so, it determines whether the tag is an html native reserved tag. If so, it directly calls the vnode class to create vnode data and return it. If not, it will go to esle if to determine whether it can obtain component information from vm.$options.components (the component content will be recorded later). If so, it will find Otherwise, it will follow the esle logic and create the vnode data of the unknown tag.
The tag, data, and children parameters are the createElement methods returned in the render function of the first Vue example:
render: createElement => createElement('div', { attrs: { id: 'app' } }, 'Hello Vue!')