vue源码分析(3-2-3、模板编译与组件化)
vue相关源码分析上一篇模板编译与组件化推荐先看总结:Vue源码汇总模板编译模板的主要用途compilation就是把模板(template)转换成渲染函数(render)
渲染函数renderrender(h){returnh('div',[h('h1',{on{click:this.handler}},'title'),h('p','somecontent')])}模板编译:Vue2.x使用VNode来描述视图和各种交互。用户自己写VNode比较复杂。用户只需要编写类HTML代码——Vue模板,模板通过编译器转换成返回VNode的render函数。vue文件在构建过程中会被webpack构建在编译版本的Vue.js中,使用template或者el设置模板。例如:
Vue模板编译过程
{{msg}}
compiles:(functionanonymous(){with(this){return_c("div",{attrs:{id:"app"}},[_m(0),_v(""),_c("p",[_v(_s(msg))]),_v(""),_c("comp",{on:{myclick:handler}}),],1);}});这里this指向vm,也就是this._c,vm._c_c就是createElement()方法,定义在instance/render.js的位置,我们从我之前的文章知道vm._c(生成虚拟dom)是一个方法渲染编译好的render,而vm.$createElement(生成虚拟dom)是渲染手写的render函数方法。其他相关的渲染函数(_开头的方法定义),在instance/render-helps/index.js,这里有一个小工具:VueTemplateExplorerVue2.6将模板编译成渲染函数的工具Vue3.0beta转换模板的过程compilingtooltemplatescompilingintorenderfunctions主要是三点分析优化生成编译模板的入口:src\platforms\web\entry-runtime-with-compiler.js调用$mount挂载模板时会调用compileToFunctions转换为渲染函数(内部调用_c()生成虚拟dom)。下面是调试compileToFunctions看生成渲染函数的过程。compileToFunctions:src\compiler\to-function.jscomplie(template,options):src\compiler\create-compiler.jbaseCompile(template.trim(),finalOptions):src\compiler\index.jsparse:解析器将模板解析成抽象语言树AST。只有将模板解析成AST后,才能根据它生成优化或代码串。src\compiler\index.js这里提供了一个工具可以查看转换后的ASTtreevueASTtreejs对象v-if/v-for结构化指令只能在编译阶段处理,如果我们要在render函数中处理条件或者循环只能使用if和forVue.component('comp',{data:(){return{msg:'mycomp'}},render(h){if(this.msg){returnh('div',this.msg)}returnh('div','bar')}})optimization-优化抽象语法树,检测子节点是否为纯静态节点。一旦检测到纯静态节点,例如:hello作为一个整体,静态节点永远不会改变,永远不会改变的节点提升为常量。重新渲染时,不会重新创建节点。patch的时候直接跳过静态子树的生成——generate将抽象语法树生成成字符串形式的js代码ASTabstractsyntaxtree很大,这里不做太多学习,主要学习概念。组件化机制组件化让我们很容易将页面拆分成多个可复用的组件组件相互独立,在系统内可复用,组件之间可以使用组件嵌套,可以像搭积木一样开发网页。从源码的角度,分析Vue组件内部是如何工作的。组件实例的创建过程是自上而下的。组件实例的挂载过程是自下而上的。全局组件的定义是Vue.component('comp',{template:'
我是一个comp
'})Vue.component()入口创建组件的构造器并挂载到vm.options.component.componentName的Vueinstance=Ctor//src\core\global-api\index.js//registerVue.directive(),Vue.component(),Vue.filter()initAssetRegisters(Vue)//src\core\global-api\资产。jsif(type==='组件'&&isPlainObject(definition)){definition.name=definition.name||iddefinition=this.options._base.extend(definition)}...//全局注册、存储资源和赋值//this.options['components']['comp']=Ctorthis.options[type+'s'][id]=definitionVue.options._base=VueVue.extend()组件构造函数创建导出函数initExtend(Vue:GlobalAPI){/***每个实例构造函数,包括Vue,有一个独一无二的*cid。这使我们能够为原型继承创建包装的“子*构造函数”并缓存它们。*/Vue.cid=0letcid=1/***类继承*/Vue.extend=function(extendOptions:Object):Function{extendOptions=extendOptions||{}//Vue构造函数constSuper=thisconstSuperId=Super.cid//从缓存加载组件的构造函数constcachedCtors=extendOptions._Ctor||(extendOptions._Ctor={})if(cachedCtors[SuperId]){returncachedCtors[SuperId]}constname=extendOptions.name||复制代码Super.options.nameif(process.env.NODE_ENV!=='production'&&name){//如果是开发环境验证组件的名称validateComponentName(name)}constSub=functionVueComponent(options){//调用_init()初始化this._init(options)}//原型继承自VueSub.prototype=Object.create(Super.prototype)Sub.prototype.constructor=SubSub.cid=cid++//合并选项Sub.options=mergeOptions(Super.options,extendOptions)Sub['super']=Super//对于props和计算属性,我们在扩展时在扩展原型上的//Vue实例上定义代理getter。这//避免为每个创建的实例调用Object.defineProperty。if(Sub.options.props){initProps(Sub)}if(Sub.options.computed){initComputed(Sub)}//允许进一步扩展/混合/插件使用Sub.extend=Super.extendSub.mixin=Super.mixinSub.use=Super.use//创建资产寄存器,所以扩展类//可以有它们的私有作为套太。ASSET_TYPES.forEach(function(type){Sub[type]=Super[type]})//启用递归自查找//将组件构造构造函数保存到Ctor.options.components.comp=Ctorif(name){Sub.options.components[name]=Sub}//在扩展时保留对超级选项的引用。//稍后在实例化时,我们可以检查Super的选项是否已更新//。Sub.superOptions=超级。optionsSub.extendOptions=extendOptionsSub.sealedOptions=extend({},Sub.options)//缓存构造函数//缓存组件的构造函数到options._CtorcachedCtors[SuperId]=SubreturnSub}}的构造函数原型component继承自vue,同样执行了init方法,调用了$mount()方法,同时生成了一个renderingwatcher,同时也验证了前面提到的一个component对应一个renderingwatcher,并缓存了组件构造函数在选项中。_Ctor后面生成vNode时,会根据Ctor创建的组件的VNode来创建根组件。组件创建和挂载组件VNode的创建过程会创建根组件。第一次_render()时,会得到整棵树的VNode结构的整体流程:newVue()->$mount()-->vm._render()-->createElement()-->createComponent()创建组件的VNode,并初始化组件的hook。当钩子函数生成组件的vnode时,初始化installComponentHooks钩子。这里调用了CreateComponentInstanceForVnode这样我们就可以串通了。当检测到虚拟domvnode是组件createComponent时,注册inithook,然后在后面调用inithook时,调用createComponentInstanceForVnode实例化组件。newvnode.componentOptions.Ctor(options)实例化调用:child.$mount(hydrating?vnode.elm:undefined,hydrating)调用组件对象的$mount(),生成渲染watcher,挂载组件到创建和页面组件实例挂载过程Vue._update()-->patch()-->createElm()-->createComponent()functioncreateComponent(vnode,insertedVnodeQueue,parentElm,refElm){leti=vnode.dataif(isDef(i)){constisReactivated=isDef(vnode.componentInstance)&&i.keepAliveif(isDef(i=i.hook)&&isDef(i=i.init)){//调用init()方法创建并挂载init()的组件实例//组件真正的DO是在这个过程中创建的M,安装在vnode.elmi(vnode,false/*hydrating*/)}//在调用init钩子之后,如果vnode是子组件//它应该创建一个子实例并安装它。子//组件也设置了占位符vnode的榆树。//在这种情况下,我们可以只返回元素并完成。if(isDef(vnode.componentInstance)){//调用钩子函数(VNode钩子函数初始化属性/event/style等,组件钩子函数)initComponent(vnode,insertedVnodeQueue)//将组件对应的DOM插入父元素insert(parentElm,vnode.elm,refElm)if(isTrue(isReactivated)){reactivateComponent(vnode,insertedVnodeQueue,parentElm,refElm)}returntrue}}}这里我们看到组件的真实DOM是在创建期间创建的执行init、init()并挂载到vnode.elm上。挂载在界面上,vnode.elm是对界面真实dom的引用。这样结合我们的三篇文章,就可以看到vue已经加载运行了。其实主要是前两篇,最后一篇只在前两篇。基于在vNode中添加组件渲染的时机。也就是说,在生成虚拟dom的时候,判断是一个组件的时候,传入相应位置的vNode,然后调用init方法对继承vue原型的组件进行初始化,然后调用$mount方法挂载到对应的vNode.elm在真实的dom上。好吧,我记得有点模糊。有时稍后再更改。本文内容来源于拉勾前端训练营