createElement
利用 createElement 方法创建 VNode
代码在:\src\core\vdom\create-element.js
const SIMPLE_NORMALIZE = 1
const ALWAYS_NORMALIZE = 2
export function createElement (
context: Component, // vm实例
tag: any, // 标签
data: any, // 数据
children: any, // 子节点,构造tree
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)
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
这里的 createElement 方法就是对参数进行“重载”,有可能开发者使用时,第三个参数传的不是data,是children。
_createElement
当前文件中:
export function _createElement (
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array<VNode> {
// 对data进行判断,参数有问题就会返回 createEmptyVNode() 生成的空 VNode
// 不允许data是响应式的
if (isDef(data) && isDef((data: any).__ob__)) {
process.env.NODE_ENV !== 'production' && warn(
`Avoid using observed data object as vnode data: ${JSON.stringify(data)}
` +
'Always create fresh vnode data objects in each render!',
context
)
return createEmptyVNode()
}
// 比如在<component :is>会有这个属性
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
if (!tag) {
// 防止 component :is 设置为 falsy
return createEmptyVNode()
}
// ... 省略代码 ...
// 处理children,判断多种情况,最终返回一个一维的VNode数组
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)
// 是否是保留标签 比如 div
if (config.isReservedTag(tag)) {
// 平台内置的元素
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// ... 省略代码 ...
} else {
// ... 省略代码 ...
} else {
// ... 省略代码 ...
}
// ... 省略代码 ...
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
上面省略的代码基本都是和组件相关的,这里先不看,只看最简单的逻辑,也就是:
当使用new Vue()传入 el: '#app'后,是如何生成的VNode,因为 <div id="app"></div> 中的div是String并且是HTML内置标签,所以会走到上面代码的
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context2
3
于是就生成了一个标签为 div 的 VNode。
上面代码中比较重要的还有children的处理, normalizeChildren 和 simpleNormalizeChildren,他们的作用都是将传入的children拍平成一维数组,数组里面的对象都是VNode
normalizeChildren
代码在 \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
}
export function normalizeChildren (children: any): ?Array<VNode> {
// 如果是基础类型,就加到数组里,设置为TextNode
return isPrimitive(children)
? [createTextVNode(children)]
: Array.isArray(children)
? normalizeArrayChildren(children)
: undefined
}
// 递归,最终将children拍平为一维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,如果是嵌套的,就进行递归
// 比如<template>, <slot>, v-for
if (Array.isArray(c)) {
if (c.length > 0) {
c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`)
// 优化:可能最后一个处理节点和下次要处理的第一个节点都是文本,那么就可以合并到一起
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)) {
res[lastIndex] = createTextVNode(last.text + c)
} else if (c !== '') {
res.push(createTextVNode(c))
}
} else {
// ... 省略代码 ...
}
}
return res
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
总结
到这一章节,回顾前几章,可以串一下VueJs的渲染过程
第一章
<html>
<body>
<div id="app"></app>
</body>
</html>2
3
4
5
new Vue({
el: '#app',
data() {
return {
name: 'Vinsea'
}
},
})2
3
4
5
6
7
8
在new Vue()时,会调用 _init() 方法,这个方法中其中一步,判断 options 中如果传入了 el 属性,那么执行 vm.$mount(vm.$options.el)
new Vue() ==> _init() ==> vm.$mount(vm.$options.el)
第二章
vm.$mount() ==> mount.call(this, el, hydrating) ==> mountComponent(this, el, hydrating) ==>new Watcher() ==> updateComponent() ==> vm._update(vm._render(), hydrating)
第三章
vm._render() ==> render.call(vm._renderProxy, vm.$createElement) ==> vm.$createElement ==> createElement()
下一章
第三章和本章是学习第二章中 vm._update(vm._render(), hydrating) 的 vm._render(),那自然,最后一步就是 vm._update()