插槽做为vue重要内容分发手段,不少同窗对它的原理比较感兴趣,下面咱们来探究一下。html
<!DOCTYPE html> <html> <head> <title>Vue事件处理</title> <script src="../../../dist/vue.js"></script> </head> <body> <div id="demo"> <h1>插槽处理机制</h1> <comp1> <span>abc</span> </comp1> </div> <script> // 声明自定义组件 Vue.component('comp1', { template: '<div><slot></slot></div>' }) // 建立实例 const app = new Vue({ el: '#demo' }); console.log(app.$options.render); </script> </body> </html> 复制代码
输出结果以下:可见只是做为span的children出现,并无什么特别vue
(function anonymous() { with(this){return _c('div',{attrs:{"id":"demo"}},[ _c('h1',[_v("插槽处理机制")]),_v(" "), _c('comp1',[_c('span',[_v("abc")])]), }) 复制代码
没有使用v-slot指令,此时组件包含内容做为父组件的children出现,内部有没有slot对编译结果没有影响。下一步就是comp1
组件实例化时会怎么作,看一下相关代码:node
// core/instance/render.js vm.$slots = resolveSlots(options._renderChildren, renderContext) 复制代码
resolveSlots()函数从父组件中获取渲染结果VNode,它们被存入default,这就是默认插槽的内容。这里的renderContext
就是父组件实例,显然若是有动态内容要从它里面获取。 markdown
这告诉咱们为何咱们能在render函数中访问
this.$slots.default
获取默认插槽内容app
那么谁在使用$slots中的内容,显然是comp1
组件的渲染函数,输出看一下:函数
(function anonymous( ) { with(this){return _c('div',[_t("default"),_v(" "), _t("foo",null,{"abc":"abc from comp"})],2)} }) 复制代码
这里的_t就是renderSlot()的别名,它会用到$slots或$scopedSlots的内容测试
//src/core/instance/render-helpers/render-slot.js const scopedSlotFn = this.$scopedSlots[name] let nodes if (scopedSlotFn) { // ... } else { nodes = this.$slots[name] || fallback } 复制代码
若是使用v-slot指令时,this
<comp> <template v-slot:default>abc</template> <template v-slot:foo="ctx">{{ctx.abc}}</template> </comp> 复制代码
编译结果将成为做用域插槽形式:spa
(function anonymous( ) { with(this){return _c('div',{attrs:{"id":"demo"}},[ _c('h1',[_v("attr update")]), _c('comp',{scopedSlots:_u([ {key:"default",fn:function(){return [_v("abc")]},proxy:true}, {key:"foo",fn:function(ctx){return [_v(_s(ctx.abc))]}}])})],1)} }) 复制代码
上面的_u是resolveScopedSlots()
的别名,v-slot的参数会做为key,值会做为fn函数的参数,好比上面的ctx。根组件首次渲染时会调用该函数,返回做用域插槽描述对象$scopedSlots
,结构以下: code
咱们知道ctx
来自于子组件,它是怎么传进来的呢?先看一下comp1
的渲染函数:
//... _t("foo",null,{"abc":"abc from comp"}) 复制代码
comp1
组件的渲染函数调用_t即renderSlot()时会将属性对象做为参数3传递,它们都来自comp1
中名称为foo的具名插槽。因此最后返回的结果是renderSlot()的返回值,也就是前面fn
的执行结果:
//src/core/instance/render-helpers/render-slot.js const scopedSlotFn = this.$scopedSlots[name] nodes = scopedSlotFn(props) 复制代码
这就解答了为何做用域插槽可以使用子组件中的数据,由于vue把做用域插槽转换为函数形式在子组件中调用了。
你们平时使用插槽时常常会记不住不一样插槽的用法,通过原理分析后咱们可以更清楚的了解到,其实无论匿名插槽、具名插槽仍是做用域插槽,最终的编译结果是一致的。既然如此彻底能够写成相一样子:
<comp1> <template v-slot:default>abc</template> <template v-slot:foo>foo</template> </comp1> 复制代码
而后只需记住若是我要的数据是父组件的仍是子组件的,若是是后者就给v-slot
设置一个属性对象值:
<comp1> <!--foo1来自承载comp1的父组件--> <template v-slot:foo>{{foo1}}</template> <!--bar1来自comp1--> <template v-slot:bar="{bar1}">{{bar1}}</template> </comp1> 复制代码
还有哪些疑问没有解答,欢迎你们留言。