实例二
该实例的模板如下:
<div id="app">
<mycom></mycom>
<div></div>
</div>
<script>
var vm = new Vue({
el:'#app',
data:{
message:123
},components:{
"mycom":{
template:'<div><aa></aa></div>',
data(){
return {
}
},
components:{
"aa":{
template:'<span></span>',
data(){
return {
}
}
}
}
}
}
})
</script>
主要是组件实例的创建流程。
实例二基本介绍
对于我们的实例二,你可能因为实例一的组件渲染过程而对实例二心存忌惮,因为实例一只是一个根组件,但是却写了那么多的流程。其实你这样想是正常的,但是我们需要明白一点,那就是实例一的过程是创建一个组件,根组件是组件,那么子组件同样也是组件,也就是说,子组件也会走根组件创建时一样的流程。也就是说,在接下来的子组件嵌套的过程中,有绝大部分的代码是相同的,所以不用担心。为了流程的简洁,所以相同的部分我们会略讲,重点不同的地方我们会详细讲解。好了,开始我们的实例二的讲解吧。
首先我们来梳理一下我们将要创建的组件的结构:
<div id="app">
<mycom></mycom>
<div></div>
</div>
<mycom>组件:
<div>
<aa></aa>
</div>
<aa>组件
<span></span>
以上是我们组件的基本结构。了解了基本结构之后我们正式的进入源码中去解析。
根组件实例的创建
首先通过new Vue
构造函数创建根组件的实例对象,我们称为vm_0
。
根组件数据初始化
然后执行vm_0._init()
函数,我们前面讲了,该函数的主要作用是对组件实例的数据初始化。我们进入该函数,主要调用:
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
获取render函数
当执行完这些后,根组件的数据初始化就基本完成了,当初始化完成之后就开始进行挂载部分。进行挂载调用的是:vm.$mount(vm.$options.el)
函数,该函数主要判断vm_0
实例对象是否有render
函数,很明显是没有,然后就会通过解析模板来生成render
函数,然后将其挂载到vm_0.render
上。
在这里重新回顾一下对于render
函数的获取流程。首先判断我们是否自己写的有render
函数,如果没有开始解析我们是否定义了template
配置,如果定义了那么就使用该配置项通过编译生成render
函数,如果我们没有定义template
,那么只能使用我们的根组件的id
所对应的模板当作template
。然后通过编译来生成render
函数。然后将生成的render
函数赋值给vm_0.render
。
当挂载完渲染函数之后,接着调用mount.call(this, el, hydrating)
函数,进行下一部分的功能模块。mount
函数内部调用mountComponent
函数。我们来看mountComponent
函数
创建watcher实例
在mountComponent
函数中,主要做了这么几件事。首先是判断render
函数,然后执行beforeMount
钩子函数,接着定义updateComponent
函数,然后执行new Watcher
。
在Watcher
构造函数中,首先创建一个与vm_0
实例对象相对应的watcher
实例对象,用这个watcher
来代表vm_0
这个组件,当watcher
创建完成之后调用watcher.get
函数。
该函数首先将watcher
压入target
栈中,用来注明现在是vm_0
组件正在进行渲染的流程。然后执行updateComponent
。该函数是用来进行挂载的,我们来具体看看该函数内部的操作。
生成根组件的vnode
进入到updateComponent
函数的内部:
updateComponent = () => {
//vm._update是在lifecycleMixin(Vue)中定义的
//vm._render是在renderMixin中定义的。
//hydrating:false
//该函数的执行其实是在new Watcher()中执行的,我们暂时只关注它的执行,不去关注在什么地方触发。
vm._update(vm._render(), hydrating)
}
首先是调用vm_0._render
函数,在实例一中我们讲过,该函数是用来生成组件的vnode
。现在我们就进入到该函数中来粗略的过一下它的具体流程。进入到_render
函数中,首先解析出render
函数,然后对vm_0
实例又挂载一些属性。然后就调用render.call
函数。render
函数的内部是这样的:
_c('div',{
attrs:{
"id":"app"}},[_c('mycom'),_v(" "),_c('div')],1)
关于_c
函数的内部处理机制,我们在实例一中已经讲解过了。但是这里我还是要讲一下,因为这里涉及到一个特殊的节点,那就是组件节点。当调用_c
函数的时候,它的内部调用的是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__)) {
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()
}
// object syntax in v-bind
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
if (!tag) {
// in case of component :is set to falsy value
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
)
}
}
// support single function children as default scoped 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)) {
// 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))) {
vnode = createComponent(Ctor, data, context, children, tag)
console.log(vnode)
} 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()
}
}
如果是正常的元素节点,那么毫无疑问的它会走:
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
这个分支,然后创建元素节点的vnode
。
遇到组件节点
但是有趣的是该节点的子节点中,有一个_c('mycom')
。也就是说它传入的tag
并不是一个原生的元素标签,而是一个我们自定义的一个标签。我们来看它会走哪个分支。很显然,他会走:
else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
debugger
vnode = createComponent(Ctor, data, context, children, tag)
console.log(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// Vue
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)//将对象变成函数
}
// if at this stage it's not a constructor or an async component factory,
// reject.
if (typeof Ctor !== 'function') {
//判断是否是一个函数
if (process.env.NODE_ENV !== 'production') {
warn(`Invalid Component definition: ${
String(Ctor)}`, context)
}
return
}
// async component
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
if (Ctor === undefined) {
// return a placeholder node for async component, which is rendered
// as a comment node but preserves all the raw information for the node.
// the information will be used for async server-rendering and hydration.
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
data = data || {
}
// resolve constructor options in case global mixins are applied after
// component constructor creation
//在组件构造函数创建后应用全局混合时解析构造函数选项
resolveConstructorOptions(Ctor)
// transform component v-model data into props & events
if (isDef(data.model)) {
transformModel(Ctor.options, data)
}
// extract props
const propsData = extractPropsFromVNodeData(data, Ctor, tag)
// functional component
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
const listeners = data.on
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn
if (isTrue(Ctor.options.abstract)) {
// abstract components do not keep anything
// other than props & listeners & slot
// work around flow
const slot = data.slot
data = {
}
if (slot) {
data.slot = slot
}
}
// install component management hooks onto the placeholder node
installComponentHooks(data)
// return a placeholder vnode
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
)
//......
return vnode
}
首先我们来解释一些参数:
Ctor:{
template: