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
functioncreateElm(vnode,insertedVnodeQueue,parentElm,refElm,nested,ownerArray,index){if(isDef(vnode.elm)&&isDef(ownerArray)){// This vnode was used in a previous render!// now it's used as a new node, overwriting its elm would cause// potential patch errors down the road when it's used as an insertion// reference node. Instead, we clone the node on-demand before creating// associated DOM element for it.vnode=ownerArray[index]=cloneVNode(vnode)}vnode.isRootInsert=!nested// for transition enter checkif(createComponent(vnode,insertedVnodeQueue,parentElm,refElm)){return}// ...}
functioncreateComponent(vnode,insertedVnodeQueue,parentElm,refElm){leti=vnode.dataif(isDef(i)){constisReactivated=isDef(vnode.componentInstance)&&i.keepAliveif(isDef(i=i.hook)&&isDef(i=i.init)){i(vnode,false/* hydrating */)}// after calling the init hook, if the vnode is a child component// it should've created a child instance and mounted it. the child// component also has set the placeholder vnode's elm.// in that case we can just return the element and be done.if(isDef(vnode.componentInstance)){initComponent(vnode,insertedVnodeQueue)insert(parentElm,vnode.elm,refElm)if(isTrue(isReactivated)){reactivateComponent(vnode,insertedVnodeQueue,parentElm,refElm)}returntrue}}}
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{constchild=vnode.componentInstance=createComponentInstanceForVnode(vnode,activeInstance)child.$mount(hydrating ? vnode.elm : undefined,hydrating)}}
Vue.prototype._init=function(options?: Object){constvm: Component=this// merge optionsif(options&&options._isComponent){// optimize internal component instantiation// since dynamic options merging is pretty slow, and none of the// internal component options needs special treatment.initInternalComponent(vm,options)}else{vm.$options=mergeOptions(resolveConstructorOptions(vm.constructor),options||{},vm)}// ...// expose real selfvm._self=vminitLifecycle(vm)initEvents(vm)initRender(vm)callHook(vm,'beforeCreate')initInjections(vm)// resolve injections before data/propsinitState(vm)initProvide(vm)// resolve provide after data/propscallHook(vm,'created')// ...if(vm.$options.el){vm.$mount(vm.$options.el)}}
exportfunctioninitInternalComponent(vm: Component,options: InternalComponentOptions){constopts=vm.$options=Object.create(vm.constructor.options)// doing this because it's faster than dynamic enumeration.constparentVnode=options._parentVnodeopts.parent=options.parentopts._parentVnode=parentVnodeconstvnodeComponentOptions=parentVnode.componentOptionsopts.propsData=vnodeComponentOptions.propsDataopts._parentListeners=vnodeComponentOptions.listenersopts._renderChildren=vnodeComponentOptions.childrenopts._componentTag=vnodeComponentOptions.tagif(options.render){opts.render=options.renderopts.staticRenderFns=options.staticRenderFns}}
exportfunctionlifecycleMixin(Vue: Class<Component>){Vue.prototype._update=function(vnode: VNode,hydrating?: boolean){constvm: Component=thisconstprevEl=vm.$elconstprevVnode=vm._vnodeconstprevActiveInstance=activeInstanceactiveInstance=vmvm._vnode=vnode// Vue.prototype.__patch__ is injected in entry points// based on the rendering backend used.if(!prevVnode){// initial rendervm.$el=vm.__patch__(vm.$el,vnode,hydrating,false/* removeOnly */)}else{// updatesvm.$el=vm.__patch__(prevVnode,vnode)}activeInstance=prevActiveInstance// update __vue__ referenceif(prevEl){prevEl.__vue__=null}if(vm.$el){vm.$el.__vue__=vm}// if parent is an HOC, update its $el as wellif(vm.$vnode&&vm.$parent&&vm.$vnode===vm.$parent._vnode){vm.$parent.$el=vm.$el}// updated hook is called by the scheduler to ensure that children are// updated in a parent's updated hook.}
vm.$el=vm.__patch__(vm.$el,vnode,hydrating,false/* removeOnly */)functionpatch(oldVnode,vnode,hydrating,removeOnly){// ...letisInitialPatch=falseconstinsertedVnodeQueue=[]if(isUndef(oldVnode)){// empty mount (likely as component), create new root elementisInitialPatch=truecreateElm(vnode,insertedVnodeQueue)}else{// ...}// ...}
然后调用 createElm,再来看下它的定义:
functioncreateElm(vnode,insertedVnodeQueue,parentElm,refElm,nested,ownerArray,index){// ...if(createComponent(vnode,insertedVnodeQueue,parentElm,refElm)){return}constdata=vnode.dataconstchildren=vnode.childrenconsttag=vnode.tagif(isDef(tag)){// ...vnode.elm=vnode.ns
? nodeOps.createElementNS(vnode.ns,tag)
: nodeOps.createElement(tag,vnode)setScope(vnode)/* istanbul ignore if */if(__WEEX__){// ...}else{createChildren(vnode,children,insertedVnodeQueue)if(isDef(data)){invokeCreateHooks(vnode,insertedVnodeQueue)}insert(parentElm,vnode.elm,refElm)}// ...}elseif(isTrue(vnode.isComment)){vnode.elm=nodeOps.createComment(vnode.text)insert(parentElm,vnode.elm,refElm)}else{vnode.elm=nodeOps.createTextNode(vnode.text)insert(parentElm,vnode.elm,refElm)}}
前面提到
createElement
创建了组件VNode,接着调用vm._update
,执行vm._patch
方法把VNode转换为真实节点,这是针对于一个普通的 VNode节点,下面看下在组件中的VNode的区别。patch
会调用createEml
创建元素节点,它在src/core/vdom/patch.js
中:和普通VNode最不一样的地方就是:
createComponent
,因为这里的vnode是一个组件vnode,所以它在创建的时候有些不太一样:首先判断
vnode.data
有没有,keepAlive先忽略,接着判断有没有data.hook
以及这个hook
中有没有init
方法,如果有就调用这个方法。回忆一下,在创建组件
createComponent
方法里面,有一个installComponentHooks
方法,这个方法会把上面定义的4个hook都初始化一遍(init,prepatch,insert,destroy),然后挂载到data.hook
上,所以在上面的判断中,init
是有的,然后就执行到了hook
上的init
方法中:keepAlive跳过,后面有个
createComponentInstanceForVnode
,这个方法返回了vnode.componentInstance
,也就是返回了vm的实例,然后调用了$mount
方法挂载子组件。看下createComponentInstanceForVnode
, 它传入了两个参数,第一个是组件vnode,第二个是activeInstance
,后面会提到。来看下createComponentInstanceForVnode
的定义:可以看到传入了两个参数,第一个是组件vnode,第二个参数其实是当前vm的一个实例,定义了一个
options
,有三个键, 中间的_parentVnode
就是父vnode,它其实是一个占位vnode,一个占位节点。最后返回了一个new vnode.componentOptions.Ctor(options)
,回忆一下,在创建子组件vnode的时候,用了一个context.$options.__base
,也就是Vue.extend
,扩展了一个子组件构造器Ctor
,接着在创建vnode的时候,有个参数是{ Ctor, propsData, listeners, tag, children }
,这里的Ctor
就是组件构造器,那么再执行vnode.componentOptions.Ctor
的时候其实就是执行了Sub
的构造函数,Sub
在src/core/global-api/extend.js
中,然后它执行 了_init
,这个_init
又回到了 Vue 的初始化,因为子组件的构造器其实是继承了 Vue 的构造器,来再次看下_init
的细节,和之前不一样的地方,在src/core/instance/init.js
:首先合并了
options
,参数options._isComponent
现在是true,所以执行initInternalComponent
,进行合并,看下这个方法:创建了一个
vm.constructor.options
对象,然后赋值给vm.$options
,接下来是重点,它把_parentVnode
和parent
传了进来,_parentVnode
就是上面的createComponentInstanceForVnode
的参数_parentVnode
,就是占位符的vnode,parent
是当前vm的实例,也就是当前子组件的父vm实例,继续看initInternalComponent
,把vnodeComponentOptions
里的一些参数拿出来赋值给opts
,到此initInternalComponent
结束。所以这里做的操作就是把通过createComponentInstanceForVnode
函数传入的参数合并到内部的$options
里了。接着看
_init
,initLifecycle
定义在src/core/instance/lifecycle.js
,看下这个方法:拿到了一个
options.parent
,这个parent
实际上就是activeInstance
,注意activeInstance
在这个文件中是一个全局变量,它的赋值在lifecycleMixin
方法中:在调用
_update
的时候,赋值了activeInstance
,也就是说每次调用_update
,就会把当前的vm实例赋值给activeInstance
:同时用
prevActiveInstance
来保留上一次的activeInstance
,这么做是什么意思?这里把当前的vm给了
activeInstance
,然后在当前vm实例的vnode在patch
的过程中,把当前实例作为父vm实例,传给子组件,这样patch
其实就是一个深度遍历,将当前激活的vm实例给activeInstance
,然后在初始化子组件的时候,将这个activeInstance
作为parent参数传入,然后在initLifecycle
里,就可以拿到当前激活的vm实例,然后把实例作为parent:此时
parent
是vm实例,parent.$children
塞一个子组件的vm。上面代码中的vm
是子组件,parent
是它的父组件,然后他们有一层push
的关系,接着把vm.$parent
赋值为父组件实例parent
,至此initLifecycle
就把这一层父子关系给建立起来了。继续看
_init
,最后的vm.$mount
是走不到的,因为现在的$options
没有el
:所以此时
_init
返回的是一个子组件的实例,然后回到createComponent
里面的init
钩子,createComponentInstanceForVnode
其实就是返回了一个子组件的实例,接着:手动调用了
$mount
来挂载,也就是执行之前的Vue.prototype.$mount
和mountComponent
方法,接着执行_update
的updateComponent
方法,最后调用__patch__
渲染VNode:然后调用
createElm
,再来看下它的定义:这里只传了2个参数,所以
parentElm
是undefined
。注意,这里传入的vnode
是组件渲染的vnode
,也就是之前说的vm._vnode
,如果组件的根节点是个普通元素,那么vm._vnode
也是普通的vnode
,这里createComponent(vnode, insertedVnodeQueue, parentElm, refElm)
的返回值是 false。接下来的过程就和createComponent
一样了,先创建一个父节点占位符,然后再遍历所有子 VNode 递归调用createElm
,在遍历的过程中,如果遇到子 VNode 是一个组件的 VNode,则重复patch,这样通过一个递归的方式就可以完整地构建了整个组件树。此时传入的
parentElm
是空,所以对组件的插入,在createComponent
有这么一段逻辑:在完成组件的整个
patch
过程后,最后执行insert(parentElm, vnode.elm, refElm)
完成组件的 DOM 插入,如果组件patch
过程中又创建了子组件,那么DOM 的插入顺序是先子后父。The text was updated successfully, but these errors were encountered: