准备
vue版本号2.6.12,为方便分析,选择了runtime+compiler版本。
回顾
如果有感兴趣的同学可以看看我之前的源码分析文章,这里呈上链接:《Vue源码分析系列:目录》
createElement的定义
createElement定义在core/vdom/create-element.js
中。
这边其实做了一个参数的重载的一个兼容性写法。如果data
是个数组或者为基本类型(其实这两种类型是恰恰对应的children
),就把data
之后的参数都往前赋值一个位置,然后将data
置空。
export function createElement(
context: Component,
tag: any,
data: any,
children: any,
normalizationType: any,
alwaysNormalize: boolean
): VNode | Array<VNode> {
/**
* 参数重载的实现
* 当data为数组时,也就是说data传入的应该是childeren
* data后面的参数全部往前移一位,赋值data为undefined
*/
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
,并讲结果返回。可以看到的是createElemet
这个函数其实是对参数做了一层封装,真正调用的还是_createElement
这个私有方法。
_createElement
接下来我们来看下_createElement
:
export function _createElement(
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array<VNode> {
//对data进行一层检查,需要的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
)}\n` + "Always create fresh vnode data objects in each render!",
context
);
return createEmptyVNode();
}
先是对data
做了一层检查,因为这里需要的data
不能是响应式的data
,
继续往下看:
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children);
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children);
}
这里先判断normalizationType
,判断这个的作用是:用不同的方法对children
做规范化,毕竟redner
函数既可以是模板编译器编译出来的,也可以是用户手写的。
- 如果是模板编译器编译的,那
children
就是可控的数据结构,更加符合Vue的预期,所以只需要做一点点简单的规范化。
是对children
做了一个规范化的处理 - 如果是用户手写的
render
那这个children
的数据结构就是不可控的,需要强规范化。
这样,我们就大致了解了为什么这里需要判断并使用不同的规范方法。
simpleNormalizeChildren
我们先进入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
}
这里使用了数组的concat
方法将一个二维数组拍成一维数组,但是更高维的不能拍平。
normalizeArrayChildren
然后再看看normalizeArrayChildren
:
export function normalizeChildren(children: any): ?Array<VNode> {
return isPrimitive(children)
? [createTextVNode(children)]
: Array.isArray(children)
? normalizeArrayChildren(children)
: undefined;
}
这边分别对children
的各种类型使用了不同的处理方法,这里我们主要看的是数组类型,也就是normalizeArrayChildren
这个方法:
/**
* 深度递归children,拍平成一个一维数组
* @param {*} children
* @param {*} nestedIndex
*/
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 (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 {
if (isTextNode(c) && isTextNode(last)) {
res[lastIndex] = createTextVNode(last.text + c.text);
} else {
if (
isTrue(children._isVList) &&
isDef(c.tag) &&
isUndef(c.key) &&
isDef(nestedIndex)
) {
c.key = `__vlist${nestedIndex}_${i}__`;
}
res.push(c);
}
}
}
return res;
}
深度优先遍历数组。
每次缓存上一个数组元素的值,还做了一个小优化:合并文本节点。
好的,我们回到'_createElement
:
let vnode, ns;
/**
* 如果tag是字符串,
* 如果tag不是字符串(是Vue组件对象),就创建一个组件赋值给vnode
*/
if (typeof tag === "string") {
let Ctor;
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag);
//如果tag是html原生标签
if (config.isReservedTag(tag)) {
// platform built-in elements
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
);
} else if (
(!data || !data.pre) &&
isDef((Ctor = resolveAsset(context.$options, "components", tag)))
) {
//这边不知道
// component
vnode = createComponent(Ctor, data, context, children, tag);
} else {
//如果tag是未知命名空间的元素,就新建一个vnode
// 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 {
//如果tag是组件对象,就创建组件
// direct component options / constructor
vnode = createComponent(tag, data, context, children);
}
判断tag
是不是组件,如果是组件就执行createComponent
(这个我们之后再讲),如果是平台保留标签(这里我以web
平台为主),就创建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();
}
对VNode做了一些判断降级处理,最终返回。
至此,createElement
阅读完毕。