vue slot-scope和v-slot

vue中插槽的使用

一、什么是插槽

官方解释:元素作为组件模板之中的内容分发插槽,传入内容后slot 元素自身将被替换。

插槽就是子组件中的提供给父组件使用的一个占位符,用<slot></slot> 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的<slot></slot>标签。

相当于组件的一块HTML模板,这块模板显示不显示、以及怎样显示由父组件来决定。

Slot 是 Vue组件的一个重要机制,它使得完全解耦的组件之间可以灵活地被组合。

二、插槽的迭代变迁

在2.5中,引入了slot-scope直接在slot元素上使用的功能,此时的slot-scope可以作用于任何标签上,也可以作为插槽使用在组件上。

<foo> 
  <bar slot-scope = "{msg}">
    {
   
   {msg}}
  </bar> 
</foo>

上面的用法导致了一个问题:slot-scope并不总是清楚地反映出哪个组件实际上提供了范围变量。这里slot-scope放置在bar组件上,但实际上是在定义默认插槽所提供的范围变量foo,而且随着嵌套加深,情况变得更糟

2.6版本为 slot 引入了一套全新的模版语法v-slot

新语法将普通的插槽 (slot) 和作用域插槽 (scoped slot) 统一在一个指令语法下,并在整体上强调明确性 (explicitness) 和一致性 (consistency)。同时,由于新语法和旧语法完全兼容,这使得可以在 2.6 中发布它。此时的v-slot只作用于<template>上;只有一种例外情况,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用,可以把 v-slot 直接用在组件上。

为什么要使用新指令而不是固定指令slot-scope?
  1. 现在使用slot-scope将是一个巨大的变化,这意味着将永远无法在2.x中发布它。
  2. 即使在3.x中对其进行了更改,更改现有语法的语义也可能导致很多学习者感到困惑,因为他们将Google视为过时的学习材料。因此,必须引入一些新的东西以区别于slot-scope。
  3. 在3.x中,计划统一插槽类型,因此不再需要(从概念上)区分作用域插槽和非作用域插槽。一个插槽可能会或可能不会接收props,但它们全都是插槽。有了这个概念上的统一,似乎没有必要将slot和slot-scope作为两个特殊属性,并且最好在单个结构下统一语法。

三、插槽的使用

  • 单个插槽 (default slot)
  • 具名插槽 (named slots)
  • 作用域插槽 (scoped slot)
  1. 默认插槽(单个插槽/匿名插槽)
    描述: 默认插槽就是指没有名字的插槽,子组件未定义名字的插槽,父级将会把未指定插槽的填充内容填充到默认插槽中。默认插槽可以放置在组件的任意位置,但是一个组件中只能有一个该类插槽
    示例代码如下:
    子组件代码定义了一个默认插槽:
<template>
    <div>
        <slot></slot>
    </div>
</template>

父组件给默认插槽填充内容:

<template>
    <div>
        <child>
            <template>
                <h1>默认插槽内容</h1>
            </template>
        </child>
    </div>
</template>

注意:

  • 父级的填充内容如果指定到子组件没有对应名字的插槽,那么该内容不会被填充到默认插槽中。
  • 如果子组件没有默认插槽,而父级的填充内容指定到默认插槽中,那么该内容就不会填充到子组件的任何一个插槽中。
  • 如果子组件有多个默认插槽,而父组件所有指定到默认插槽的填充内容,将会全都填充到子组件的每个默认插槽中。
  1. 具名插槽
    描述:具名插槽其实就是给插槽取个名字。一个子组件可以放多个插槽,且可以放在不同的地方,而父组件填充内容时,可以根据这个名字把内容填充到对应插槽中。
    例如
    子组件的代码,设置了两个插槽(header和footer):
<template>
    <div>
        <div>
            <slot name="header"></slot>
        </div>
        <div>
            <slot name="footer"></slot>
        </div>
    </div>
</template>

父组件填充内容, 父组件通过 v-slot:[name] 的方式指定到对应的插槽中,2.6.0 新增(v-slot:) 缩写替换为字符 #

<template>
    <div>
        <child>
            <template v-slot:header>
                <h1>头部内容</h1>
            </template>
            <template #footer>
                <h1>尾部内容</h1>
            </template>
            //2.6.0版本之前的写法
            <h1 slot="header">头部内容</h1>
            <h1 slot="footer">尾部内容</h1>
        </child>
    </div>
</template>

注意:

  • 父级的填充内容如果指定到子组件的没有对应名字插槽,那么该内容不会被填充到默认插槽中。

  • 如果子组件没有默认插槽,而父级的填充内容指定到默认插槽中,那么该内容就不会填充到子组件的任何一个插槽中。

  1. 作用域插槽
    描述:作用域插槽其实就是带数据的插槽,即带参数的插槽,简单的来说就是子组件提供给父组件的参数,该参数仅限于插槽中使用,父组件可根据子组件传过来的插槽数据来进行不同的方式展现和填充插槽内容。作用域插槽要求在slot上面绑定数据
    例如下面子组件定义了一个top插槽:
<div class="child4">
     <slot name="top" :data="item"></slot>
</div>
<child4>
     <template #top="slotProps">
          <div>{
   
   {slotProps.data}}</div>
     </template>
  	 <template #top="{data}">   //解构插槽 
          <div>{
   
   {data}}</div>
     </template>
</child4>

//2.6.0版本之前的使用
<div class="child3">
     <slot name="top" :data="item"></slot>
</div>
<child3>
     <div slot="top" slot-scope="slotprop">{
   
   {slotprop.data}}</div>
</child3>

常用场景之一
如果子组件中的某一部分的数据,每个父组件都会有自己的一套对该数据的不同的呈现方式,这时就需要用到作用域插槽。

四、slot, slot-scope 以及 v-slot, v-slot:[name] = slotProps 与 $slots, $scopedSlots 的关系

s l o t s 用 来 访 问 被 插 槽 分 发 的 内 容 ; slots用来访问被插槽分发的内容; slots访scopedSlots用来访问作用域插槽,对于包括 默认 slot 在内的每一个插槽,该对象都包含一个返回相应 VNode 的函数。

一般在应用中不涉及渲染函数,很少用得到$slots$scopedSlots,在使用渲染函数书写一个组件时及在深入理解插槽的实现和二次封装 UI库 的时候就可能用得上了。

1. 无作用域插槽

<div id="app">
    <div id="parent-template">
    	<child>
        <!--2.5.11-->
        <p slot="header">header</p>
        <p slot="footer">footer</p>
        <p>default</p>
        <!--2.6.11-->
        <template v-slot:header>header</template>
        <template v-slot:footer>footer</template>
        <template>default</template>
      </child>
		</div>
</div>
<template id="child-template">
    <div>
        <slot name="header"></slot>
        <slot>默认分发处</slot>
        <slot name="footer"></slot>
    </div>
</template>
<script>
  // 注册子组件
Vue.component("child", {
   template:"#child-template",
   mounted() {
      console.log('$slots',this.$slots)
      console.log('$scopedSlots',this.$scopedSlots)
   }
});
  // 初始化父组件
new Vue({
   el: "#parent-template"
});
</script>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mLp09Sd7-1588226114276)(./images/slot2.5.11.jpg)]

​ v2.5.11无作用域插槽.jpg

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4yMJ8s9w-1588226114318)(./images/slot2.6.11.jpg)]

​ v2.6.11无作用域插槽.jpg

从这个结果中可以看出v2.6.0以前,slot 和 slot-scope是分开的 ;

v2.6.0以前父组件没有调用 slot-scope 时, $scopedSlots 里面为空 ;

v2.6.0以后 $scopedSlots 里的具名值和 $slots 具名值是一一对应的。

2. 有作用域插槽

<div id="app">
    <div id="parent-template">
      <child>
        <!--此处是待分发的内容-->
        <!--2.5.11-->
        <p slot="header" slot-scope="{data}">{
   
   {data}}</p>
        <p slot="footer" slot-scope="scopedata">{
   
   {scopedata.data}}</p>
        <p>default</p>
        <!--2.6.11-->
        <template v-slot:header="headerdata">{
   
   {headerdata.data}}</template>
        <template v-slot:footer="footerdata">{
   
   {footerdata.data}}</template>
        <template v-slot:default>default</template>        
      </child>
    </div>
    <template id="child-template">
      <div>
        <slot name="header" :data="header"></slot>
        <slot>默认分发处</slot>
        <slot name="footer" :data="footer"></slot>
      </div>
    </template>
</div>
<script>
  // 注册子组件
Vue.component("child", {
  template:"#child-template",
  data() {
    return {
      header: '我是头部内容',
      footer: '我是底部内容'     
    }
  },
  mounted() {
    console.log('$slots',this.$slots)
    console.log('$scopedSlots',this.$scopedSlots)
  }
});
// 初始化父组件
new Vue({
    el: "#parent-template"
});
</script>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mtZGVuE6-1588226114334)(./images/scoped2.5.11.jpg)]

​ v2.5.11有作用域插槽.jpg

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yee7yZDu-1588226114338)(./images/scoped2.6.11.jpg)]

​ v2.6.11有作用域插槽.jpg

v2.6.0以前,在父组件中’header’具名插槽中调用了slot-scope后,$scopedSlots 中出现了‘header’为key的function, $slots 中‘header’为key的VNode不见了;

v2.6.0以后,在父组件中’header’具名插槽中调用了slot-scope后,$scopedSlots 中没有变化, $slots 中‘header’为key的VNode不见了。一开始$slots里面没有值,是后面塞进去的

3. 多次调用同一个具名插槽

<div id="app">
    <div id="parent-template">
      <child>
        <template v-slot:header>
          <p>header</p>
        </template>
        <template v-slot:header>
          <p>header1</p>
        </template>
        <template v-slot:footer>
          <p>footer</p>
        </template>
        <template v-slot:default>
          <p>default</p>
        </template>
      </child>
    </div>
    <template id="child-template">
      <div>
        <slot name="header" :data="header"></slot>
        <slot>默认分发处</slot>
        <slot name="footer" :data="footer"></slot>
      </div>
    </template>
  </div>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uc3q5MKK-1588226114343)(./images/more-slot2.5.11.jpg)] v2.5.11多次调用同一个具名插槽

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-umr8LNfA-1588226114350)(./images/more-slot.jpg)]

​ v2.6.11多次调用同一个具名插槽

从输出可以看出,新式写法多次调用同一个具名插槽,后面的会覆盖前面的,这个和老是写法一个比较大的区别,在升级的时候就要特别注意。

经过上面的实践对比,有以下几点结论:

  1. v2.6.0以后在$slots里可以取到具名key,都可以通过 $scopedSlots 访问到;
  2. v2.6.0以后v-slot的写法不能多次使用同名的具名插槽,后面的会覆盖前面的,不会显示两个同名具名插槽的内容;
  3. v2.6.0以后用v-slot的写法,如果在子组件中用$slot,一定调用watch方法,因为一开始$slots为空,后面才塞入的数据

总结

语法:

2.6版本之前 slot=[name] slot-scope="data"

2.6版本之后 v-slot:[name] v-slot:[name]="data" 虽然已经废弃2.6之前的语法,但也支持不会报错

在3.0中,最终删除slot-scope,并且仅支持新语法v-slot

性能优化:

v2.6 版本后对 slotslot-scope 做了一次统一的整合,让它们全部都变为函数的形式,所有的插槽都可以在 this.$scopedSlots 上直接访问,这让我们在开发高级组件的时候变得更加方便。

在 2.5 的版本中,由于生成 slot 的作用域是在父组件中,所以明明是子组件的插槽 slot 的更新是会带着父组件一起更新的;在2.6中 scoped slot 在编译时生成的是一个函数,这个函数被传入子组件之后会在子组件的渲染函数中被调用。这意味着 scoped slot 的依赖会被子组件收集,那么当依赖变动时就只会直接触发子组件更新了,尽可能避免不必要的渲染。

猜你喜欢

转载自blog.csdn.net/kang_k/article/details/105860274
今日推荐