vm._render
Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
)
}
vm.$vnode = _parentVnode
let vnode
try {
currentRenderingInstance = vm
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render`)
if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
} catch (e) {
handleError(e, vm, `renderError`)
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
} finally {
currentRenderingInstance = null
}
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
if (!(vnode instanceof VNode)) {
if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
)
}
vnode = createEmptyVNode()
}
vnode.parent = _parentVnode
return vnode
}
_render方法实际调用的是options中的render函数。如果用户没有提供render函数,则会通过编译模板生成。通过调用render函数生成一个vnode,然后返回这个vnode。render函数的参数是$createElement也就是我们平常使用的createElement函数。调用render函数实际就是获取createElement函数的返回值。
render: function (createElement) {
return createElement('div', {
attrs: {
id: 'app'
},
}, this.message)
}
$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.$createElement函数是用户手写提供的render函数,而vm._c是编译后生成的render函数。它们都调用createElement函数。a代表标签,b代表数据对象,c是子节点数组。
createElement
const SIMPLE_NORMALIZE = 1
const ALWAYS_NORMALIZE = 2
function createElement (
context: Component,
tag: any,
data: any,
children: any,
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)
}
当用户在render函数中使用createElement($createElement)时,因为数据对象参数可选,所以用户可能直接传标签和子节点。因此需要判断参数b的类型是不是对象,如果不是对象则需要对参数进行处理:没传数据对象所以data是undefined,这里传进来的data其实是子节点children。用户的createElement情况下规范子节点的类型是ALWAYS_NORMALIZE即2。处理完参数后会调用_createElement。
_createElement
export function _createElement (
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array<VNode> {
if (isDef(data) && isDef((data: any).__ob__)) {
//warn
return createEmptyVNode()
}
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
if (!tag) {
return createEmptyVNode()
}
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 (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)) {
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))) {
vnode = createComponent(Ctor, data, context, children, tag)
} else {
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
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()
}
}
如果data数据对象是一个响应式对象则会抛出警告,并且返回一个空的注释vnode节点。如果data数据对象中有is属性,则说明它是一个动态组件,则将is的值作为tag。如果动态组件的is属性是一个假值时,tag为false,则返回一个空的注释vnode节点。如果data数据对象中有key属性,会检测key是否是string或者number类型,否则抛出警告。如果子节点数组中只有一个函数时,将它当作默认插槽,然后清空子节点列表。根据不同的类型将子元素进行标准化处理。
如果标签tag是平台保留tag,类似div,span....。如果平台保留标签上的事件v-on有.native指令会抛出错误,因为.native只能在组件上。最后会通过new VNode创建虚拟DOM。
如果tag是一个自定义组件,在this.$options.components对象中找到指定标签名称的组件构造函数通过createComponent创建组件的VNode。
对于不知名的一个标签,也生成 VNode,因为考虑到在运行时可能会给一个合适的名字空间。
如果tag为非字符串,比如可能是一个组件的配置对象或者是一个组件的构造函数,通过createComponent创建组件的VNode。
createComponent
export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
if (isUndef(Ctor)) {
return
}
const baseCtor = context.$options._base
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
if (typeof Ctor !== 'function') {
if (process.env.NODE_ENV !== 'production') {
warn(`Invalid Component definition: ${String(Ctor)}`, context)
}
return
}
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
if (Ctor === undefined) {
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
data = data || {}
resolveConstructorOptions(Ctor)
if (isDef(data.model)) {
transformModel(Ctor.options, data)
}
const propsData = extractPropsFromVNodeData(data, Ctor, tag)
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
const listeners = data.on
data.on = data.nativeOn
if (isTrue(Ctor.options.abstract)) {
const slot = data.slot
data = {}
if (slot) {
data.slot = slot
}
}
installComponentHooks(data)
const name = Ctor.options.name || tag
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
if (__WEEX__ && isRecyclableComponent(vnode)) {
return renderRecyclableComponentTemplate(vnode)
}
return vnode
}
如果组件的构造函数不存在则直接return。如果Ctor是一个对象,则说明Ctor是一个配置对象,context.$options._base就是Vue,通过Vue.extend将Ctor转为构造函数。经过操作后如果Ctor依然不是一个函数则表示这是一个无效的组件定义。
对于异步组件的处理:为异步组件返回一个占位符节点,组件被渲染为注释节点,但保留了节点的所有原始信息,这些信息将用于异步服务器渲染。
然后通过resolveConstructorOptions对组件做选项合并。当编译器将组件编译为渲染函数,渲染时执行render函数,然后执行其中的 _c时,这里会解析构造函数选项,并合基类选项,以防止在组件构造函数创建后应用全局混入。
通过transformModel将组件的v-model的信息(值和回调)转换为 data.attrs 对象的属性、值和data.on对象上的事件、回调。
通过extractPropsFromVNodeData提取props数据,得到propsData对象,propsData[key] = val,以组件props配置中的属性为key,父组件中对应的数据为 value。
处理函数式组件:执行函数式组件的render函数生成组件的 VNode,做了以下 3 件事:1、设置组件的 props 对象,2、设置函数式组件的渲染上下文,传递给函数式组件的render函数,3、调用函数式组件的render函数生成vnode。
获取事件监听器对象 data.on,因为这些监听器需要作为子组件监听器处理,而不是 DOM 监听器。
将带有.native修饰符的事件对象赋值给data.on。
如果是抽象组件,则值保留 props、listeners 和 slot。
通过installComponentHooks在组件的data对象上设置hook对象,hook 对象增加四个属性:init、prepatch、insert、destroy,负责组件的创建、更新、销毁,这些方法在组件的 patch 阶段会被调用。最后创建组件的Vnode。
resolveConstructorOptions
function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions
if (superOptions !== cachedSuperOptions) {
Ctor.superOptions = superOptions
const modifiedOptions = resolveModifiedOptions(Ctor)
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
return options
}
这个方法在Vue初始化时也会用到,当Vue初始化时,不存在父类,所以直接返回Vue构造函数的options。 如果存在父类,则递归调用resolveConstructorOptions去解析获得父类构造函数的options。从子类的superOptions属性上获取缓存的父类构造函数的options,与解析到的真正的父类构造函数的options相比较。如果不相同,则说明父类的配置项发生了更改,更新子类的superOptions属性。通过resolveModifiedOptions找到更改的选项,将更改的选项和extend选项合并,然后将新的选项赋值给options,并更新子类构造函数的options。
resolveModifiedOptions
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
let modified
const latest = Ctor.options
const sealed = Ctor.sealedOptions
for (const key in latest) {
if (latest[key] !== sealed[key]) {
if (!modified) modified = {}
modified[key] = latest[key]
}
}
return modified
}
将之前的optins,也就是之密封(浅拷贝)的options,与最新的options进行比较找出不同点。
transformModel
function transformModel (options, data: any) {
const prop = (options.model && options.model.prop) || 'value'
const event = (options.model && options.model.event) || 'input'
;(data.attrs || (data.attrs = {}))[prop] = data.model.value
const on = data.on || (data.on = {})
const existing = on[event]
const callback = data.model.callback
if (isDef(existing)) {
if (
Array.isArray(existing)
? existing.indexOf(callback) === -1
: existing !== callback
) {
on[event] = [callback].concat(existing)
}
} else {
on[event] = callback
}
}
在data的attrs对象上存放v-model的值,在data的on对象上存放v-model事件。获取v-model中事件对应的回调函数,然后看原来的on中是否存放了这个事件名,如果没存放则添加,如果已经存放了则合并成数组。
extractPropsFromVNodeData
function extractPropsFromVNodeData (
data: VNodeData,
Ctor: Class<Component>,
tag?: string
): ?Object {
const propOptions = Ctor.options.props
if (isUndef(propOptions)) {
return
}
const res = {}
const { attrs, props } = data
if (isDef(attrs) || isDef(props)) {
for (const key in propOptions) {
const altKey = hyphenate(key)
if (process.env.NODE_ENV !== 'production') {
const keyInLowerCase = key.toLowerCase()
if (
key !== keyInLowerCase &&
attrs && hasOwn(attrs, keyInLowerCase)
) {
//tip
}
}
checkProp(res, props, key, altKey, true) ||
checkProp(res, attrs, key, altKey, false)
}
}
return res
}
如果未定义props直接返回。否则便利porps,将小驼峰形式的key转换为连字符形式。
checkProp
function checkProp (
res: Object,
hash: ?Object,
key: string,
altKey: string,
preserve: boolean
): boolean {
if (isDef(hash)) {
if (hasOwn(hash, key)) {
res[key] = hash[key]
if (!preserve) {
delete hash[key]
}
return true
} else if (hasOwn(hash, altKey)) {
res[key] = hash[altKey]
if (!preserve) {
delete hash[altKey]
}
return true
}
}
return false
}
判断 hash(props/attrs)对象中是否存在key或altKey,存在则设置给 res => res[key] = hash[key]