Vue源码分析——组件化
上一篇介绍了Vue从实例化到渲染到页面的具体过程。基于这篇文章,本文介绍了从组件创建到渲染的具体过程。我们将从源码入手,从一个角度来分析Vue的组件内部是如何工作的。只有了解了内部工作原理,我们才能更轻松地使用它。vue源码比较复杂,需要耐心反复理解调试。不懂的可以多调试问百度。罗马不是一天建成的。相信坚持会有收获~先写个测试页面,具体调试可以下载vue.js,然后具体做断点调试器调试。
首先分析上图中的流程,绿线是组件化流程的方向。1、执行render函数我们从上一篇文章可以看出,页面渲染主要是执行vm._update(vm._render(),hydrating),即先执行vm._render()函数获取vnodevirtualdom,然后vm._update(vnode)将virtualdom更新到页面,对应实际开发,即上面测试页面中的render(h){returnh(Child)},其中h为createElement,而createElement是在参数重载后调用的_createElement_createElement主要是根据标签判断是组件还是普通节点标签,返回对应的vnode虚拟dom,所以核心在于createComponent函数function_createElement(context,tag,data,children,normalizationType){if(typeoftag==='string'){//普通元素创建虚拟domvnode=newVNode(config.parsePlatformTagName(tag),data,children,undefined,undefined,context);}else{//组件创建虚拟domvnode=createComponent(tag,data,context,children);}}2。执行createComponent函数createComponent的逻辑也会有些复杂,但是建议分析源码只分析核心流程,后面可以针对分支流程,所以这里组件渲染的这三个关键步骤:createVue.extendfunction//Vue.options._base=Vue在Vue初始化initGlobalAPI时定义//baseCtor.extend(Ctor)等价于Vue.extend(Ctor)constbaseCtor=context.$options._baseif(isObject(Ctor))){Ctor=baseCtor.extend(Ctor)}然后看看enxtend函数如何构建VueComponent的构造函数Vue.extend=function(extendOptions){//thisisVuevarSuper=this;//创建一个VueComponent构造函数varSub=functionVueComponent(options){//当我们去实例化Sub时,它会再次执行this._init逻辑到Vue实例初始化逻辑this._init(options);};//Object.create继承Super,即继承VueSub.prototype=Object.create(Super.prototype);//改变构造函数指向自身,实现完美继承Sub.prototype.constructor=Sub;sub.cid=cid++;Sub.options=mergeOptions(Super.options,extendOptions);子['超级']=超级;returnSub}可以通过Object.create学习继承类,这也是面试中经常遇到的问题。VueComponent继承Vue,然后合并选项,最后返回到Sub构造函数。installcomponenthookfunction//将组件管理钩子安装到占位符上执行将在后面补丁流程的介绍中详细介绍。实例化vnode//返回一个占位符vnodevarname=Ctor.options.name||标签;varvnode=newVNode(("vue-component-"+(Ctor.cid)+(name?("-"+name):'')),data,undefined,undefined,undefined,context,{Ctor:Ctor,propsData:propsData,listeners:监听器,tag:标签,children:children},asyncFactory);returnvnode通过以上三步可以知道组件的实例化是通过Vue.extend中的VueComponent构造函数实现的,createComponent最终返回vnode执行vm._update(vnode)渲染页面。3.执行_update函数_update执行patch,即调用路径函数functionpatch(oldVnode,vnode,hydrating,removeOnly){//通过vnodecreateElm(vnode,insertedVnodeQueue,oldElm._leaveCb?null:parentElm,nodeOps.nextSibling(oldElm));//删除旧节点if(isDef(parentElm)){removeVnodes(parentElm,[oldVnode],0,0);}elseif(isDef(oldVnode.tag)){invokeDestroyHook(oldVnode);}}路径的核心在于createElm,通过createElm将虚拟dom创建为真实dom,参见createElmfunctioncreateElm(vnode,insertedVnodeQueue,parentElm,refElm,nested,ownerArray,index){//if如果是一个组件,执行createComponentif(createComponent(vnode,insertedVnodeQueue,parentElm,refElm)){return}//createChildren递归调用createElm生成vnodecreateChildren(vnode,children,insertedVnodeQueue);if(isDef(data)){//真正的dom元素属性构造invokeCreateHooks(vnode,insertedVnodeQueue);}//父元素插入真正的dom元素insert(parentElm,vnode.elm,ref榆树);}createComponent函数执行leti=vnode.dataif(isDef(i=i.hook)&&isDef(i=i.init)){i(vnode,false);}在createComponent的第二步,通过ExecuteinstallComponentHooks(data),挂vnode.data和init等钩子函数,i(vnode,false);即执行下面的初始化函数varcomponentVNodeHooks={init:functioninit(vnode,hydrating){if(vnode.componentInstance&&!vnode.componentInstance._isDestroyed&&vnode.data.keepAlive){//keep-alivecomponents,treat作为补丁varmountedNode=vnode;//绕过流componentVNodeHooks.prepatch(mountedNode,mountedNode);}else{//获取Vue.extend构造函数实例varchild=vnode.componentInstance=createComponentInstanceForVnode(vnode,activeInstance);//挂载实例child.$mount(hydrating?vnode.elm:undefined,hydrating);}},//....}这里主要通过createComponentInstanceForVnode实例化VueComponent,获取实例并挂载functioncreateComponentInstanceForVnode(vnode,//我们知道它是MountedComponentVNode但流不是父节点//处于生命周期状态的activeInstance){varoptions={_isComponent:true,_parentVnode:vnode,parent:parent};//检查内联模板渲染函数varinlineTemplate=vnode.data.inlineTemplate;如果(isDef(inlineTemplate)){options.render=inlineTemplate.render;options.staticRenderFns=inlineTemplate.staticRenderFns;}//vnode.componentOptions.Ctor相当于VueComponentreturnnewvnode.componentOptions.Ctor(options)}合并options并设置_isComponent为true,然后newVueComponent(options),因为VueComponent是Vue的子类,那么实例化就会重新执行Vue._init(options)。同样再次执行vm._update(vm._render(),hydrating)。最后会执行回createElm,或者继续贴出createElm的代码。functioncreateElm(vnode,insertedVnodeQueue,parentElm,refElm,nested,ownerArray,index){//如果是组件,执行createComponentif(createComponent(vnode,insertedVnodeQueue,parentElm,refElm)){return}//createChildren递归调用createElm生成vnodecreateChildren(vnode,children,insertedVnodeQueue);if(isDef(data)){//真正的dom元素属性构造invokeCreateHooks(vnode,insertedVnodeQueue);}//父元素插入真正的dom元素insert(parentElm,vnode.elm,refElm);}如果组件没有子组件,createComponent(vnode,insertedVnodeQueue,parentElm,refElm)为false,则逐级跳出递归,返回patch函数渲染页面。其实vue组件化过程就是先newvue,然后执行init初始化,然后判断是否有组件,如果没有,直接生成vode,最后通过path渲染vnode到页面。如果有组件,使用createComponent函数创建VueComponent构造器(继承自Vue)。然后在打补丁的时候通过createElm函数判断是否有组件,然后调用VueComponent函数,然后再次进行init初始化,直到没有实例化组件,再执行createElm创建一个真正的dom元素,最后渲染到页面上.具体可以参考上面的流程图进行分析!