CreateElement is used to create a VNode
1. About the vNode
Since createElement returns a VNode, it’s important to understand some of the concepts of a VNode.
- about
vnode
The constructor can be seenhere vnode
isjs
Object. Avoid frequent DOM manipulation to improve performance.- The component’s
vnode
There are two:- A placeholder
vnode
:vm.$vnode
Only component instances - Apply colours to a drawing
vnode
:vm._vnode
You can use thisVNode
It maps directly to realityDOM
- They are father-son relationships:
vm._vnode.parent = vm.$vnode
- The concepts are introduced here, and will be covered in the component Patch section
- A placeholder
2. createElement
CreateElement returns a VNode
// src/core/vdom/create-element.js
// Constant definition
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> {
// Parameter overload, which means that the data argument is actually optional
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)
}Copy the code
CreateElement is basically a wrapper around _createElement that does two things:
- If the third argument is an array or primitive type (excluding null and undefined), then the argument is overloaded.
- judge
alwaysNormalize
Whether it istrue
thennormalizationType = ALWAYS_NORMALIZE
// The internal function of the render function call generated by template compilation
vm._c = (a, b, c, d) = > createElement(vm, a, b, c, d, false)
// to be used by user-written render functions
vm.$createElement = (a, b, c, d) = > createElement(vm, a, b, c, d, true)
Copy the code
Note:
- Written manually by the user
render
Function,normalizationType
It must be constantALWAYS_NORMALIZE
- Generated by compilation
render
Function,normalizationType
According to the callvm._c
Depending on the specific value passed in
The main purpose of normalizationType is to distinguish how children should be regulated, which we’ll discuss below
The next step is to execute the actual handler, _createElement
3. _createElement
// 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> {
// There are some edge cases that need not be concerned for the moment:
// 1. The passed data parameter cannot be the observed data
// 2. Dynamic component processing
// 3. A warning is thrown if the key value is not a primitive type
// 4. support single function children as default scoped slot
// Core logic 1: normalize chidlren
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
// Core logic 2: Create a Vnode
let vnode, ns
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
// Whether to keep tags native to HTML
if (config.isReservedTag(tag)) {
if(process.env.NODE_ENV ! = ='production' && isDef(data) && isDef(data.nativeOn)) {
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
)
// Is the registered component name
} else if((! data || ! data.pre) && isDef(Ctor = resolveAsset(context.$options,'components', tag))) {
vnode = createComponent(Ctor, data, context, children, tag)
// Unknown or unlisted namespace elements
// and so on are checked at runtime, because a namespace may be assigned to a child when its parent normalizes it
} else {
vnode = new VNode(
tag, data, children,
undefined.undefined, context
)
}
} else {
// direct component options / constructor
vnode = createComponent(tag, data, context, children)
}
/ / return vnode
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()
}
}
Copy the code
3.1 Standardized children
- if
normalizationType
Is a constantALWAYS_NORMALIZE
, that is, user-writtenrender
Delta function, so let’s use delta functionnormalizeChildren
To normalize the child nodes - if
normalizationType
Is a constantSIMPLE_NORMALIZE
, then usesimpleNormalizeChildren
Why we need normalizationchildren
?
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. There are two cases where extra normalization is needed:
Translation:
The template compiler tries to avoid normalization by statically analyzing the template at compile time.
For string templates with pure HTML tags, it’s possible to skip the normalization altogether because it ensures that the generated render function returns Array
.
But there are two cases that require additional normalization:
3.1.1 simpleNormalizeChildren
// src/core/vdom/helpers/normalize-children.js
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
}
Copy the code
children
Can contain functional components. The functional component returns an array instead of a root node. ifchildren
We have an array, and we need to flatten it, so that’s going to bechildren
Convert to a one-dimensional array.- use
Array.prototype.concat
itfaltten
. Since a functional component has normalized its own child levels, the depth is guaranteed to be only 1 level.
3.1.2 normalizeChildren
// src/core/vdom/helpers/normalize-children.js
export function normalizeChildren (children: any): ?Array<VNode> {
return isPrimitive(children)
? [createTextVNode(children)]
: Array.isArray(children)
? normalizeArrayChildren(children)
: undefined
}
Copy the code
The following situations require a call to normalizeChildren to normalize
- handwritten
render
Function orJSX
When,children
Allows primitive types to be written to create a single simple text node, which is calledcreateTextVNode
To create a text nodeVNode
- When compiling
<template>
,slot
,v-for
Generates a nested array, which is callednormalizeArrayChildren
methods
Take a look at normalizeArrayChildren below:
// src/core/vdom/helpers/normalize-children.js
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]
// If it is a nested array
if (Array.isArray(c)) {
if (c.length > 0) {
// recursive processing
c = normalizeArrayChildren(c, `${nestedIndex || ' '}_${i}`)
// If there are two consecutive text nodes, they are merged into a single text node
if (isTextNode(c[0]) && isTextNode(last)) {
res[lastIndex] = createTextVNode(last.text + (c[0] :any).text)
c.shift()
}
res.push.apply(res, c)
}
// If it is a primitive type
} else if (isPrimitive(c)) {
if (isTextNode(last)) {
// If there are two consecutive text nodes, they are merged into a single text node
// This is necessary for SSR tells, because text nodes are essentially merged when rendered to HTML
res[lastIndex] = createTextVNode(last.text + c)
} else if(c ! = =' ') {
// convert primitive to vnode
res.push(createTextVNode(c))
}
// It is already a VNode
} else {
if (isTextNode(c) && isTextNode(last)) {
// If there are two consecutive text nodes, they are merged into a single text node
res[lastIndex] = createTextVNode(last.text + c.text)
} else {
// If children is a list and nestedIndex exists, update the key (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
}
Copy the code
NormalizeArrayChildren receives 2 parameters:
children
Represents the child node to be normalizednestedIndex
Represents nested indexes because of a singlechild
It could be an array type
The main logic of normalizeArrayChildren is to traverse children, get a single node C, and then determine the type of C
- An array of: recursive call
normalizeArrayChildren
- The base typeThrough:
createTextVNode
Method convert toVNode
type - Vnode typeIf:
children
Is av-for
List, then according tonestedIndex
To update itskey
.- Generated during compilation
render
In the functionvm._l(...)
Will be calledrenderList
Function, which will mount one_isVList
The variable is used to indicate that this isv-for
The list of - when
v-for
A normal HTML tag is automatically processedkey
.v-for
A component when a componentkey
If notundefined
- Generated during compilation
// src/core/instance/render-helpers/render-list.js
export function renderList (
val: any.
render: (
val: any.
keyOrIndex: string | number.
index? :number
) => VNode
): ?Array<VNode> {
// ...
(ret: any)._isVList = true
return ret
}
Copy the code
In the traversal process, the following is done for all three cases: if there are two consecutive text nodes, they are merged into a single text node.
After normalization of children, children becomes an Array of type VNode
3.2 create vnode
When tag is a string:
- call
config.isReservedTag
Function judgment iftag
If it is a built-in tag, create a corresponding oneVNode
Object. - if
tag
If it is a registered component namecreateComponent
Function. tag
Is an unknown tag name, which will be created directly by the tag namevnode
, and then wait until runtime to check, because its parent may assign namespaces to its children when they normalize.
When tag is not a string:
- through
createComponent
Of the component typeVNode
We’ll talk about that later
conclusion
Each VNode has children. Each child element is a VNode. This creates a VNode Tree. It describes our DOM Tree very well.
Vue source code 1.3: $mount
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
Copy the code
Now that we know how vm._render creates a VNode, the next step is to render the VNode into a real DOM, using vm._update, which we’ll look at in the next chapter.