You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
if(typeoftag==='string'){letCtor// 获取tag的命名空间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=newVNode(config.parsePlatformTagName(tag),data,children,undefined,undefined,context)}elseif((!data||!data.pre)&&isDef(Ctor=resolveAsset(context.$options,'components',tag))){// 在vm的options的components选项查找,如果存在则是组件节点vnode=createComponent(Ctor,data,context,children,tag)}else{// 未知的元素vnode=newVNode(tag,data,children,undefined,undefined,context)}}else{// direct component options / constructorvnode=createComponent(tag,data,context,children)}
letasyncFactoryif(isUndef(Ctor.cid)){asyncFactory=CtorCtor=resolveAsyncComponent(asyncFactory,baseCtor)if(Ctor===undefined){// 异步组件一开始用空的注释节点做占位// return a placeholder node for async component, which is renderedreturncreateAsyncPlaceholder(asyncFactory,data,context,children,tag)}}
constcomponentVNodeHooks={init(vnode: VNodeWithData,hydrating: boolean): ?boolean{if(vnode.componentInstance&&!vnode.componentInstance._isDestroyed&&vnode.data.keepAlive){// kept-alive components, treat as a patchconstmountedNode: any=vnode// work around flowcomponentVNodeHooks.prepatch(mountedNode,mountedNode)}else{// 创建组件实例const child =vnode.componentInstance=createComponentInstanceForVnode(vnode,activeInstance// 组件占位符所在的vm)child.$mount(hydrating ? vnode.elm : undefined,hydrating)}},prepatch(oldVnode: MountedComponentVNode,vnode: MountedComponentVNode){// ...},insert(vnode: MountedComponentVNode){// ...},destroy(vnode: MountedComponentVNode){// ..}}consthooksToMerge=Object.keys(componentVNodeHooks)// 合并对应阶段的钩子functionmergeHook(f1: any,f2: any): Function{constmerged=(a,b)=>{// flow complains about extra args which is why we use anyf1(a,b)f2(a,b)}merged._merged=truereturnmerged}functioninstallComponentHooks(data: VNodeData){consthooks=data.hook||(data.hook={})for(leti=0;i<hooksToMerge.length;i++){constkey=hooksToMerge[i]constexisting=hooks[key]consttoMerge=componentVNodeHooks[key]if(existing!==toMerge&&!(existing&&existing._merged)){hooks[key]=existing ? mergeHook(toMerge,existing) : toMerge}}}
组件化是Vue的核心概念,它让我们提取可复用的模版和脚本,在需要用到的地方插入对应的组件标签。那Vue是怎么把组件渲染成真实的DOM的呢,它和我们渲染普通的HTML有啥区别?我们在上篇文章中实例插入一个组件,通过源码分析组件怎么渲染?
组件虚拟节点
我们通过
new Vue()
插入一个组件节点:在入口文件挂载:
从上篇分析
new Vue()
的主流程后,我们知道vue实例会调用vm.$mount()
进行挂载。然后新建一个实例的渲染watcher,并执行下面的updateComponent
函数:该函数先调用
vm._render()
生成一个虚拟节点,很明显这是一个App组件节点。所以我们来看看_createElement()
对组件节点是怎么创建的。它定义在src/core/vdom/create-element.js
:上面是创建虚拟节点的关键代码。因为tag是一个App组件的配置对象,所以调用
createComponent()
方法生成一个组件虚拟节点。这个方法定义在src/core/vdom/create-component.js
:这个方法一开始先获取
context.$options._base
,也就是Vue构造函数。然后调用Vue.extend()
返回组件的构造器。它定义在src/core/global-api/extend.js
:这个方法一开始先缓存组件构造器到组件配置对象的
extendOptions._Ctor
中,在后面再用到该组件,直接返回组件构造器。然后定义组件构造器:所以新建一个组件实际入口也是
vm._init()
方法。然后指定原型为Vue.prototype
让它拥有Vue原型方法。接下来处理组件的option配置,它调用mergeOptions()
方法把Vue.options
上的属性合并到组件构造器的options,比如平时我们全局注册的组件,就是在这步进行合并,让我们组件内直接使用而不用声明。最后,把Vue上面的静态方法也赋值到组件构造器,最后返回这个构造器。回到
createComponent()
方法,如果创建组件配置不是一个对象,证明是一个异步组件,所以下面就是对异步工厂函数的处理:里面的实现细节我们之后再分析。接下来是处理
v-model
,props
等,我们暂且跳过。然后到了下面这个方法:这个方法主要是安装组件vnode在接下来patch过程需要调用的钩子函数。它的定义:
显而易见,它把
componentVNodeHooks
对象定义的各种阶段钩子合并到了我们新建的组件vnode的hook属性上面。至此我们组件vnode就创建完成并返回,输出下返回的结果:创建组件实例
现在我们已经得到组件的虚拟DOM,正确的说是组件占位vnode。因为它在渲染后会被组件对应的DOM替换,比如我们例子的
<App/>
组件。执行完
render
后调用Vue实例的vm._update()
方法,该方法里面会走下面的方法:接下来的过程上一篇一样。会进行
patch()
方法并执行oldvnode为真实DOM的条件逻辑,不同的是createElm()
方法里面会执行到下面代码为true
并返回,因为我们此时vnode为组件虚拟节点:来看下
createComponent
方法的定义:这个方法会先调用vnode上面的init钩子函数,init钩子主要是创建组件实例,并且得到组件真实DOM节点。我们看下init钩子的定义。在定义我们前面创建组件vnode的地方:
这个方法
createComponentInstanceForVnode
创建组件的实例。再看这个方法实现之前,现在说下activeInstance
变量的意义。它是当前Vue或者组件实例,作为一个全局变量,主要维护组件树对应的实例对象的父子关系。在vm._update()方法里面处理:setActiveInstance()
方法定义:很简单,先把之前的实例对象存起来,再设置成最新调用update的实例。在patch完后就恢复原来的实例。
现在来看下
createComponentInstanceForVnode()
方法的定义:这个方法主要通过
new vnode.componentOptions.Ctor(options)
创建一个组件实例,然后调用vm._init()
方法回到我们之前创建Vue实例的逻辑,但是有两个不同的地方。一个是合并配置,它会走下面的处理:因为组件构造器已经在extend的时候合并了Vue的全局属性,没必要再合并一次。最后组件实例不会走下面方式的挂载:
组件的挂载定义在init钩子,如下:
创建组件真实的DOM
接下来就走挂载的逻辑,这个和前面Vue实例的挂载一样。最后也是会走到
vm.__patch__()
方法,不同的是vm.$el
是undefined
。所以在patch
函数里面,它会执行下面条件逻辑:这个逻辑就是调用
createElm()
方法把App组件对应的DOM虚拟节点转为真实的DOM,并且存储在vnode.elm上。方法我们上一篇分析过,这里不再累赘。这我们例子中也就是下面对应的DOM:patch()
返回vnode.elm
作为vm.__patch__()
的结果存在vm.$el
。这里的vm
就是我们App组件实例。也就是我们我们已经得到App组件的真实DOM,并存在vm.$el
上面,也就是App组件虚拟节点的vnode.componentInstance.$el
。执行完App组件实例的
updateComponents()
函数,流程回到App虚拟节点的init()
钩子。然后执行下面的代码:通过
initComponent
方法会把组件对应的真实DOM赋值给vnode.elm
,注意这是vnode是组件App的占位虚拟节点,然后调用insert()
方法把组件插入DOM中。至此,我们App组件就渲染完成:总结
其实,Vue组件化的渲染过程是一个深度遍历的过程。假如我们例子中的App组件中包含其他组件,也会先创建对应占位vnode。 在App实例调用
update
的时候创建包含组件的实例,创建组件的真实DOM并插入到App组件的DOM中,如此反复递归。最后new Vue
获取最终的DOM节点插入真实DOM环境。通过上面的分析,我们可以知道组件DOM插入是先子后父的,并且它满足树的深度优先遍历。
The text was updated successfully, but these errors were encountered: