我们知道VNode是由_render函数生成的:
vnode = render.call(vm._renderProxy, vm.$createElement);
在$createElement
中:
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false);
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true);
VNode
的创建是跟createElement
有关系。
// src/core/vdom/create-element.js
// wrapper function for providing a more flexible interface
// without getting yelled at by flow
export function createElement (
context: Component,
tag: any,
data: any,
children: any,
normalizationType: any,
alwaysNormalize: boolean
): VNode | Array<VNode> {
// 判断data是否时数组或者原始类型(不包括null、undefined)
// 在我们一开始写的例子,在页面上解析message的案例,改变成render函数时,data是string,所以不用往前移动
if (Array.isArray(data) || isPrimitive(data)) {
// 参数都往前移动一位
normalizationType = children
children = data
data = undefined
}
// vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false);
// vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true);
if (isTrue(alwaysNormalize)) {
// const SIMPLE_NORMALIZE = 1
// const ALWAYS_NORMALIZE = 2
normalizationType = ALWAYS_NORMALIZE
}
// 到这一步可以看出来createElement是_createElement的封装
return _createElement(context, tag, data, children, normalizationType)
}
// 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> {
// 判断data是否含有__ob__属性, Vue中被j监听的data都会添加上__obj__
if (isDef(data) && isDef((data: any).__ob__)) {
// 抛出警告的代码(省略)
// 返回空的VNode
return createEmptyVNode()
}
// 判断是否时动态组件(即有没有is属性)
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
// 如果tag是false
if (!tag) {
// 返回空的VNode
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
)
}
}
// 跟插槽有关的代码省略
// ...
// 规范化处理
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
let vnode, ns
// 如果tag是string类型
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
// 判断是不是html的内置标签
if (config.isReservedTag(tag)) {
// platform built-in elements
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
// 判断tag是不是已注册的组件名
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// 一个未知的标签名,这里会直接按标签名创建 VNode
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
// direct component options / constructor
vnode = createComponent(tag, data, context, children)
}
// 最后对vnode又做了一次校验,最后返回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()
}
}
// template解析成render后会执行的函数,将把二维数组拍平成一维数组
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
}
// 用户自定义写的render函数
export function normalizeChildren (children: any): ?Array<VNode> {
// 如果时自定义类型,则返回一个文本节点,例如例子上的message,直接解析成文本节点
return isPrimitive(children)
? [createTextVNode(children)]
: Array.isArray(children)
// 如果不是 再做处理,最后也是返回一个一维数组
? normalizeArrayChildren(children)
: undefined
}
总结
VNode
是由_render
中的createElement
返回的,而createElemnt
实际上是对_createElement
的封装。在_createElement
中会对参数进行处理(转为需要的格式)并进行判断,这些参数后期都会成为VNode
所需的参数的值,传递的data
不能是被监听过的值,tag
可以是字符串(为内置的html、或者已经组册过的组件,如果都不是,也会创建VNode
,然后运行时再检查),也可以是组件的名字,最后会再次检查生成的VNode
,并返回。
下一节学习如何将生成的VNode
渲染在视图上,即成为真实的DOM。