当前位置: 首页 > Web前端 > vue.js

Vueslot实现原理

时间:2023-03-31 14:52:27 vue.js

前言vue.js的灵魂是组件,组件的灵魂是slot。借助插槽,我们可以最大限度地重用组件。本文主要详细总结了slot的实现机制,在某些场景下有用。知其然,知其所以然,掌握vue.js的实现原理,不仅可以提高自己解决问题的能力,还可以学习大佬们的编程思想和开发范式。示例代码这是默认槽这是命名插槽这是一个作用域插槽(旧版本){{scope.test}}Thisisascopedslot(newversion){{scopeProps.test}}透过现象看本质slot的作用是实现内容distribution,要实现内容分发,需要两个条件:placeholder分发内容组件内部定义的slot标签,我们可以理解为占位符,父组件中slot的内容就是要分发的内容。槽处理的本质是将指定的内容放到指定的位置。废话不多说,从本文中,你将能够明白:slots的实现原理如何在render方法中使用slots的实现原理vue组件的实例化顺序是:父组件状态初始化(data,computed,watch...)-->模板编译-->生成render方法-->实例化renderingwatcher-->调用render方法,生成VNode-->patchVNode,转换为真正的DOM-->实例化子组件-->.....重复同样的过程-->将子组件生成的真实DOM挂载到父组件生成的真实DOM上并挂载到页面-->移除旧节点从上面的过程可以推断那:父组件模板在子组件之前解析,所以父组件会先获取slot模板内容,子组件模板在后面解析,所以当子组件调用render方法生成VNode时,可以使用一些意味着得到slotDomainslots的VNode节点函数可以获取子组件中的变量,所以scopedslots的VNode生成是动态的,即需要实时传递子组件的作用域。整个slot的处理阶段大致分为三步:编译生成渲染模板生成VNode以下面的代码为例简单概括一下slot运行的过程。123

父组件的编译阶段就是解析将模板文件转换成AST语法树会将槽模板解析为以下数据结构:'template',//...parent:parentASTNode,children:[childASTNode],//槽内容子节点,即文本节点123slotScope:undefined,//作用域槽绑定值slotTarget:"\"hello\"",//命名槽名称slotTargetDynamic:false//是否为动态绑定槽//...}]}父组件根据AST语法树生成渲染方法,解析生成渲染方法字符串,并最后父组件生成结果如下。这个结构和我们直接写的render方法是一致的。本质就是生成VNode,只不过_c或者h是this.$createElement的缩写。with(this){return_c('div',{attrs:{"id":"app"}},[_c('test',[_c('template',{slot:"hello"},[_v("\n123\n")])],2)],1)}父组件生成VNode,调用render方法生成VNode。VNode的具体格式如下:{tag:'div',parent:undefined,data:{//存放VNode配置项attrs:{id:'#app'}},context:componentContext,//componentscopeelm:undefined,//真正的DOM元素children:[{tag:'vue-component-1-test',children:undefined,//component是页面的最小组件单元,slot内容放在sub-component用于解析parent:undefined,componentOptions:{//组件配置项Ctor:VueComponentCtor,//组件构造方法data:{hook:{init:fn,//实例化组件调用方法insert:fn,prepatch:fn,destroy:fn},scopedSlots:{//scopedslot配置项,用于生成scopedslotVNodeslotName:slotFn}},children:[//componentslotnodetag:'template',propsData:undefined,//propsparameterlisteners:未定义,数据:{插槽:'你好'},children:[VNode],parent:undefined,context:componentContext//父组件作用域//...]}}],//...}在vue中,组件是页面结构的基本单元,从上面的VNode我们也可以看出VNode页面层级结束于测试组件,测试组件children处理会在vue-dev\src\core中处理子组件初始化过程中的子组件构造方法组装和属性合并\在vdom\create-component.js的createComponent方法中,组件实例化调用入口在vue-dev\src\core\vdom\patch.js的createComponent方法中。子组件状态初始化实例化子组件时,会在initRender->resolveSlots方法中将子组件槽节点挂载到组件作用域vm中,挂载形式为vm.$slots={slotName:[VNode]}形式。子组件编译阶段在编译阶段,子组件会将slot节点编译成如下AST结构:{tag:'h1',parent:undefined,children:[{tag:'slot',slotName:"\"hello\"",//...}],//...}子组件生成的渲染方法如下,其中_t是renderSlot方法的缩写。从renderSlot方法中,我们可以直观的对比一下slot的内容是不是和Slot的点数是绑在一起的。//渲染方法with(this){return_c('h1',[_t("hello")],2)}//源码路径:vue-dev\src\core\instance\render-helpers\render-slot.jsexportfunctionrenderSlot(name:string,fallback:?Array,props:?Object,bindObject:?Object):?Array{constscopedSlotFn=this.$scopedSlots[name]让节点if(scopedSlotFn){//作用域槽props=props||{}if(bindObject){if(process.env.NODE_ENV!=='production'&&!isObject(bindObject)){warn('slotv-bindwithoutargumentexpectsanObject',this)}props=extend(extend({},bindObject),props)}//作用域插槽,获取插槽VNodenodes=scopedSlotFn(props)||fallback}else{//获取slot普通插头SlotVNodenodes=this.$slots[name]||fallback}consttarget=props&&props.slotif(target){returnthis.$createElement('template',{slot:target},nodes)}else{returnnodes}}作用域槽和命名槽的区别<divid='app'>{{scope.hello}}
与普通槽相比,作用域槽的主要区别在于槽的内容可以从作用域中获取ofsubcomponents变量需要注入子组件变量。与namedslots相比,scopedslots有以下区别:scopingslots在组装渲染方法时,会生成一个包含注入作用域的方法,该作用域由createElement生成,VNode多了一层注入作用域方法包装,这也决定了slotVNodescopeslot是在子组件生成VNode时生成的,namedslot是在父组件创建VNode时生成的。_u为resolveScopedSlots,用于将节点配置项转换成{scopedSlots:{slotName:fn}}的形式。with(this){return_c('div',{attrs:{"id":"app"}},[_c('test',{scopedSlots:_u([{key:"hello",fn:function(scope){return[_v("\n"+_s(scope.hello)+"\n")]}}])})],1)}当子组件初始化时,它会处理命名槽节点和挂载到组件$slots中,在renderSlot中直接调用scopeslot,其他过程大致相同。slotaction的机制不难理解,但关键是模板解析和渲染函数生成这两个步骤内容较多,流程较长,理解起来也比较困难。使用技巧通过上面的分析,可以对slot的处理流程有一个大概的了解。大部分工作使用模板编写Vue代码,但有时模板有一定的局限性,需要使用render方法来放大Vue的组件抽象能力。然后在render方法中,我们的slot的使用如下:namedslotslot处理一般分为两部分:父组件父组件只需要写成模板编译的渲染方法,即指定slotslotname子组件由于子组件直接取父组件初始化阶段生成的VNode,所以子组件只需要将slotlabel替换为父组件生成的VNode,子组件就会挂载named当组件处于初始化状态时,将插槽添加到组件的$slots属性中。
ScopeslotScopeslot使用起来更灵活,可以注入子组件状态。Scopeslot+render方法对于二次组件封装非常有用。例如,scopeslots在封装基于JSON数据的ElementUI表格组件时非常有用。
不断变化