插槽作为vue重要内容分发手段,很多同学对它的原理比较感兴趣,下面我们来探究一下。
<!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出现,并没有什么特别
(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
组件实例化时会怎么做,看一下相关代码:
// core/instance/render.js vm.$slots = resolveSlots(options._renderChildren, renderContext) 复制代码
resolveSlots()函数从父组件中获取渲染结果VNode,它们被存入default,这就是默认插槽的内容。这里的renderContext
就是父组件实例,显然如果有动态内容要从它里面获取。
这告诉我们为什么我们能在render函数中访问
this.$slots.default
获取默认插槽内容
那么谁在使用$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指令时,
<comp> <template v-slot:default>abc</template> <template v-slot:foo="ctx">{{ctx.abc}}</template> </comp> 复制代码
编译结果将成为作用域插槽形式:
(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
,结构如下:
我们知道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把作用域插槽转换为函数形式在子组件中调用了。
还有哪些疑问没有解答,欢迎大家留言。