经过compile
编译模板字符串变成了render
函数,在src/core/instance/render.js
中,通过vnode = render.call(vm._renderProxy, vm.$createElement)
调用了render
方法并最终返回了一个VNode
对象实例,即Vue
中的虚拟Dom
,基本定义如下
export default class VNode {
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions,
asyncFactory?: Function
) {
this.tag = tag v // 标签名
this.data = data // 结点相关属性数据
this.children = children // 子节点
this.text = text // 文本
this.elm = elm // dom元素
this.ns = undefined // 命名空间
this.context = context // VNode上下文对象
......
this.key = data && data.key // key
this.componentOptions = componentOptions // VNode对象如果对应的是一个自定义组件,componentOptions保存组件相关事件、props数据等
this.componentInstance = undefined // VNode对象如果对应的是一个自定义组件,componentInstance保存相对应的vue实例
this.parent = undefined // 当前自定义组件在父组件中的vnode
this.raw = false
this.isStatic = false // 是否静态节点
.....
this.isOnce = false // 是否为v-once元素的VNode对象
}
get child (): Component | void {
return this.componentInstance
}
}
后面还定义了一些创建简单的VNode的方法,如createEmptyVNode
、reateEmptyVNode
、createTextVNode
、cloneVNode
前面说过,调用_init方法的最后,会调用vm._render来生成VNode节点,在src/core/instance/render.js
可以找到这个方法,
在这个方法里面,就会调用vnode = render.call(vm._renderProxy, vm.$createElement)
来生成VNode
,其中在这文件有两个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)
其中vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
是编译模板生成的render函数执行时调用的,
vm.$createElement
是自己添加render
函数时,作为参数传递给render函数
src/core/vdom/create-element.js
中createElement
export function createElement (
context: Component, // 当前的上下文对象,哪一个vm
tag: any, // 标签名
data: any, // 节点相关的属性,比如key,ref,attrs等等属性
children: any, // children
normalizationType: any, // 子元素扁平化处理的级别
alwaysNormalize: boolean // 总是扁平化处理
): VNode | Array<VNode> {
// 如果判定为true,说明该元素没有相关属性
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
export function _createElement (
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array<VNode> {
......
if (!tag) {
// 如果tag为空,创建一个空的VNode
return createEmptyVNode()
}
// 在Vue中,如果设置对象或者数组为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
)
}
}
// 如果只有一个children,则当成slot解析
if (Array.isArray(children) &&
typeof children[0] === 'function'
) {
data = data || {}
data.scopedSlots = { default: children[0] }
children.length = 0
}
// 扁平化处理
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)
if (config.isReservedTag(tag)) {
// 如果tag为String,且为平台保留的标签,直接创建VNode
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// 如果不是保留标签,并且有数据,没有设置跳过,且是自定义组件
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// 否则,创建VNode对象
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
// 如果tab不是字符串,也是直接createComponent
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()
}
}
createComponent
可以创建自定义组件
export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
// 如果Ctor为空,跳过此步骤,对应的实际情况就是传入的参数并不是字符串或者自定义组件
if (isUndef(Ctor)) {
return
}
// baseCtor其实就是指的Vue对象
const baseCtor = context.$options._base
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
......
data = data || {}
// 递归合并父对象上的options
resolveConstructorOptions(Ctor)
// 处理v-model指令
if (isDef(data.model)) {
transformModel(Ctor.options, data)
}
// 抽取props属性
const propsData = extractPropsFromVNodeData(data, Ctor, tag)
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
// 处理组件.native的事件,表示组件上的事件
const listeners = data.on
data.on = data.nativeOn
// 处理抽象组件中的slot,如keep-alive
if (isTrue(Ctor.options.abstract)) {
const slot = data.slot
data = {}
if (slot) {
data.slot = slot
}
}
// 合并一些钩子函数
installComponentHooks(data)
const name = Ctor.options.name || tag
// 生成VNode
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
......
return vnode
}
extractPropsFromVNodeData
function extractProps (data: VNodeData, Ctor: Class<Component>, tag?: string): ?Object {
const propOptions = Ctor.options.props
// 组件没有props,return
if (!propOptions) {
return
}
const res = {}
// domProps表示input的value、option的selected等属性
// attrs表父组件绑定在子元素上的属性值
const { attrs, props, domProps } = data
if (attrs || props || domProps) {
for (const key in propOptions) {
// 将驼峰命名转化为连线命名
// aB -> a-b
const altKey = hyphenate(key)
.....
// 遍历找到props、attrs、domProps中的属性
// 如果没有传递preserve参数,则表示找到该key的值时删除对应的属性
// 最后返回res,并且只留下props上的属性
checkProp(res, props, key, altKey, true) ||
checkProp(res, attrs, key, altKey) ||
checkProp(res, domProps, key, altKey)
}
}
return res
}
最后,对于自定义组件,会创建一个vnode
对象,并返回一个标签名为vue-component-cid-name
格式的VNode
对象。
上面说的扁平化处理其实就是将多维的数组,合并转换成一个一维的数组
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
}
normalizeChildren
export function normalizeChildren (children: any): ?Array<VNode> {
return isPrimitive(children)
// 如果传入的children是字符串或者数字,则直接返回文本结点数组
? [createTextVNode(children)]
: Array.isArray(children)
// 如果传入的children是数组,调用normalizeArrayChildren
? normalizeArrayChildren(children)
: undefined
}
normalizeArrayChildren
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]
// 如果该元素为undefined或null或Boolean类型的值,continue
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 = 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)) {
// 合并last.text和c
res[lastIndex] = createTextVNode(last.text + c)
} else if (c !== '') {
// 如果最后一个节点不是文本节点,且不为空
// 创建一个空文本节点
res.push(createTextVNode(c))
}
} else {
// 如果c是一个文本VNode对象,且res中最后一个元素也是文本结点
// 合并两个VNode为一个
if (isTextNode(c) && isTextNode(last)) {
res[lastIndex] = createTextVNode(last.text + c.text)
} else {
// 设置VNode的key
if (isTrue(children._isVList) &&
isDef(c.tag) &&
isUndef(c.key) &&
isDef(nestedIndex)) {
c.key = `__vlist${nestedIndex}_${i}__`
}
res.push(c)
}
}
}
return res
}