彻底搞懂slot插槽,图文详解

1、什么是插槽

VUE官方文档的解释:

Vue 实现了一套内容分发的 API,将 <slot> 元素作为承载分发内容的出口。

个人理解:

插槽实质是对子组件的扩展,通过<slot>插槽向子组件内部指定位置传递内容。

或者这么说:

<slot> 的出现是为了父组件可以堂而皇之地在子组件中加入内容。

打个比方:

有句话叫一个萝卜一个坑。父组件想要在子组件中种萝卜,需要在子组件中挖个坑, <slot> 就是一个【萝卜坑】。父组件想要给子组件添加的内容就是【萝卜】。

由此可见,萝卜种不种,种什么萝卜由父组件控制;萝卜坑在哪,由子组件控制。 换言之 ——

插槽显不显示、怎样显示是由父组件来控制的,而插槽在哪里显示就由子组件来进行控制。

举个栗子,在 Father 父组件内部使用名为 Child 的子组件,向子组件的内部的“指定位置”传递一些内容,你可以这么写:

<!-- 这是子组件-->
<div class="child">
  <h2>Child的标题</h2>
  <slot>我是一个萝卜坑</slot>
</div>
<!-- 这是父组件-->
<div class="father">
  <h1>Father的标题</h1>
  <Child>
    我是个萝卜~
  </Child>
</div>

Father的标题
Child的标题
我是个萝卜~

看到没,是不是就像父组件的萝卜种到了子组件的坑里

看到这里不禁发出灵魂一问:这不就是父组件给子组件传了个值吗?那和props有啥区别?

2、与 props 的区别

通过props属性,父组件只能向子组件传递属性、方法

<template>
  <div>
    <h1>我是A</h1>
    <child :to-child-data="parentData" :to-child-fun="parentFun"></child>
  </div>
</template>
<script>
import child from './B.vue'
export default {
  name:'A',
  components: {
    child
  },
  data () {
    return {
      say: '',
      parentData: "hello"
    };
  },

  methods: {
    parentFun(val) { //自定义的函数 val是子组件给的参数
      this.say = val; //献给data一份问候~
      console.log(val," from child"); //看看参数有没有值
      console.log(this.say," from parent"); //看看能不能给父组件的data传进去
    },
  }
};
</script>
<template>
  <div>
    <h2>我是B</h2>
    <el-button @click="childClick">子组件调用父组件的props方法</el-button>
  </div>
</template>
<script>
export default {
  name:'B',
  data () {
    return {
    };
  },
  props: {
    toChildFun: {  
      type: Function, //参数类型:函数
      required: true //是否必填:是
    },
    toChildData: {  
      type: String, //参数类型:String
      default: '' //默认值
    }
  },
  methods: {
    childClick(event)  {
      this.$props.toChildFun('这是来自子组件的问候~~'); //调用Props传来的方法,并送他一个参数~~
      // this.toChildFun('这是来自子组件的问候~~'); //跟上面一个效果
    }
  }
};
</script>
微信图片_20220413123345.png

而插槽还可以传递带标签的内容、甚至是组件:

<!-- 这是父组件哦-->
<div class="father">
  <h1>Father的标题</h1>
  <Child>
    {{username}}                    <!-- 参数-->
    <button>我是一个按钮</button>  <!-- 带标签的内容-->
    <Child2></Child2>             <!-- 组件-->
  </Child>
</div>

3、插槽的使用

(1)匿名插槽(又叫单个插槽、默认插槽)

就是没有设置name属性的插槽。

<slot>这是个匿名插槽(没有name属性),这串字符是匿名插槽的默认值。</slot>

1)可以放置在组件的任意位置。
2)一个组件中只能有一个匿名插槽。
3)匿名插槽只能作为没有slot属性的元素的插槽。

<div class="child">
    <h1>子组件</h1>
     <slot name="head">头部默认值</slot>
     <slot name="body">主体默认值</slot>
     <slot>这是个匿名插槽(没有name属性),这串字符是匿名插槽的默认值。</slot>
 </div>
<div class="parent">
 <h1>父组件</h1>
 <child>
     <p slot="body">我是主体</p>
     <p>我是其他内容</p>
     <p slot="footer">我是尾巴</p>
 </child>
 </div>

运行结果 :
父组件
子组件
头部默认值 (具名插槽<slot name="head">的默认值被渲染,因为父组件没有为此插槽提供内容)
我是主体 (具名插槽<slot name="body">的默认值被覆盖)
我是其他内容 (匿名插槽的默认值被覆盖)

注意:

1、<p slot="footer">我是尾巴</p> 插槽被丢弃了,因为子组件中没有<slot name="footer">的插槽与之匹配。
2、 如果子组件中的匿名插槽不存在,则<p>我是其他内容</p>也会被丢弃。

这个例子就是想说明两点:

1、坑会一直在,但是没有找到坑的萝卜就会被丢弃!
2、后来者居上,后面的萝卜会覆盖原来坑里的萝卜

(2)具名插槽

意思就是具有名字的插槽,名字通过属性name来定义。

<slot name="body">这是个具名插槽(有name属性),这串字符是具名插槽的默认值。</slot>

一个组件中可以有很多具名插槽,出现在不同的位置。

<!-- <base-layout>组件-->
<div class="container">
    <header>
        <slot name="header"></slot>
    </header>
    <main>
        <slot></slot> <!-- 一个不带 name 的 <slot> 出口会带有隐含的名字“default”。-->
    </main>
    <footer>
        <slot name="footer"></slot>
    </footer>
</div>

除了上面那个例子中,把slot直接用在普通标签或者<template>上,更推荐在 <template>元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供插槽名称,这样就可以定义插槽的内容了:

<base-layout>
  <template v-slot:header>
    <h1>我是头header</h1>
  </template>

  <p>我是main的内容111</p>   
  <p>我也是main的内容222</p>

  <template v-slot:footer>
    <p>我是footer</p>
  </template>
</base-layout>

1、带有 v-slot 的<template> 元素中的所有内容都将会被传入相应的插槽。

2、任何没有被包裹在带有 v-slot 的 <template> 中的内容都会被视为默认插槽的内容。

如果你希望更明确一些,可以在一个 <template> 中包裹默认插槽的内容:

<base-layout>
  <template v-slot:header>
    <h1>我是头header</h1>
  </template>

  <template v-slot:default> 
    <p>我是main的内容111</p>
    <p>我也是main的内容222</p>
  </template>

  <template v-slot:footer>
      <p>我是footer</p>
  </template>
</base-layout>

以上两种写法的渲染效果是一样的:

<font color="red">注意:</font> v-slot 只能添加在 <template> 上。 (只有一种例外情况),请继续往下看。

(3)作用域插槽

上面props的例子,可以看到 父组件传给子组件了一个属性和一个方法,子组件可以使用 props 中的属性和方法。那对于插槽来说,父组件想访问子组件的数据,又该怎么做呢?

<!-- 这是子组件<Child> -->
<template>
  <div>
    <h1>hey,我是组件Child的标题</h1>
    <slot></slot>
  </div>
</template>
<script>
export default {
  data() {
     return {
        childUser: { Name:"Tom", Age: 23 }
    }
}
</script>

当Father使用Child组件时,想访问Child中的数据 childUser 并且将其展示在插槽的位置:

<!-- 这是父组件<Father>-->
<div>
  <h1>hey,我是父组件Father的标题</h1>
  <Child>
    {{childUser.Name}}    
  </Child>
</div>

然而上述代码不会正常工作,因为

父级模板里的所有内容都是在父级作用域中编译的;子级模板里的所有内容都是在子作用域中编译的。

只有 <Child> 组件可以访问到 childUser,而我们提供的内容【childUser.Name、childUser.Age】是在父级<Father> 中渲染的。

为了让 childUser 在父级的插槽内容中可用,需要把 childUser 从 <Child>子级作用域传递到 <Father>父级作用域

做法就是将 childUser 作为 <slot> 元素的一个属性绑定上去:

<!-- <Child> 组件: -->
<template>
  <div>
    <h1>hey,我是组件Child的标题</h1>
    <slot v-bind:childData="childUser"></slot>
  </div>
</template>
<script>
export default {
  data() {
     return {
        childUser: { Name:"Tom", Age:23 }
    }
}
</script>

绑定在 <slot> 元素上的属性childData 被称为插槽 prop。

现在,在父级作用域中,我们可以使用带值的 v-slot 来定义 插槽 prop 的名字:

<!-- 这是父组件<Father>-->
<div>
  <h1>hey,我是父组件Father的标题</h1>
  <Child>
    <template v-slot:default="slotProps">
      {{ slotProps.childData.Name}}
      {{ slotProps.childData.Age}}
    </template>
  </Child>
</div>

在这个例子中,我们将[ 包含所有插槽 prop 的对象 ] 命名为 slotProps(自定义)。

因为在上述情况下(<font color="red">这里就是上面说的那一种例外情况</font>),被提供的内容只有默认插槽,组件的标签可以被当作插槽的模板来使用。这样我们就可以把 v-slot 直接用在组件上:

<!-- 这是父组件哦-->
<div>
  <h1>hey,我是父组件Father的标题</h1>
  <Child v-slot:default="slotProps">
      {{ slotProps.childData.Name}}
      {{ slotProps.childData.Age}}
  </Child>
</div>

还可以省略default。就像未指明的内容对应默认插槽一样,不带参数的 v-slot 被假定对应默认插槽:

<!-- 这是父组件哦-->
<div>
    <h1>hey,我是父组件Father的标题</h1>
    <Child v-slot="slotProps">
      {{ slotProps.childData.Name }}
      {{ slotProps.childData.Age}}
    </Child>
</div>

但是默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确:

<!-- 无效,会导致警告 -->
<Child  v-slot="slotProps">
  {{ slotProps.childData.Name }}
  <template v-slot:other="otherSlotProps">
    slotProps is NOT available here
  </template>
</Child >

只要出现多个插槽,请始终为所有的插槽使用完整的基于 <template> 的语法:

<!-- 这是子组件 -->
<template>
  <div>
    <h1>hey,我是组件Child的标题</h1>
    <slot v-bind:childData="childUser"></slot>       <!--匿名插槽-->
    <slot name="other" v-bind:otherChildData="otherChildUser"></slot>  <!--具名插槽-->
  </div>
</template>
<script>
export default {
  data() {
     return{
        childUser: {Name:"Tom",Age:23},
        otherChildUser:{Name:"Tom",Age:23}
    }
}
</script>
<!-- 这是父组件-->
<Child >
  <template v-slot:default="slotProps">
    {{ slotProps.childData.Name }}
  </template>

  <template v-slot:other="otherSlotProps">
    {{otherSlotProps.otherChildData.Name}}
  </template>
</Child>

将 childUser 作为 <slot> 元素的一个属性绑定上去,除了上述第一种写法,在实践中发现还可以省略 v-bind: 后面的参数:
[图片上传失败...(image-d4115-1652236435855)]

<div>
  <h1>hey,我是组件Son的标题</h1>
  <slot v-bind="childUser"></slot>
</div>
<div>
    <h1>hey,我是父组件Father的标题</h1>
    <Son>
      <template v-slot:default="user">
        {{ user.Name}},
        {{ user.Age}}
      </template>
    </Son>
  </div>

(4)解构插槽 Prop

作用域插槽的内部工作原理是将你的插槽内容包裹在一个拥有单个参数的函数里

所以,这意味着 v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式

<!-- <Child> 组件: -->
<template>
  <div>
    <h1>hey,我是组件Child的标题</h1>
    <slot v-bind:childData="childUser"></slot>
  </div>
</template>
<script>
export default {
  data() {
     return {
        childUser: { Name:"Tom", Age:23 }
    }
}
</script>
<!-- <Father> 组件: -->
<Child  v-slot="{childData}">
  {{ childData.Name }}
</Child>

将 childData重命名为 person:

<!-- <Father> 组件: -->
<Child v-slot="{ childData: person }">
  {{ person.Name }}
</Child  >

你甚至可以定义默认内容,用于插槽 prop 是 undefined 的情形:

<!-- <Father> 组件: -->
<Child v-slot="{ childData= { Name: 'Guest' } }">
  {{ childData.Name }}
</Child >

对象的解构赋值看不明呗的请移步 ====>> 变量的解构赋值—ES6入门,第六节。

4、v-slot、slot-scope 和 slot

slotslot-scope 已经被废弃,所有的 2.x 版本中 slotslot-scope 属性仍会被支持,但已经被官方废弃且不会出现在 Vue 3 中。

所以更推荐使用vue2.6.0中的 v-slot

(1)v-slot 的使用

1、在一个 <template>元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称。

<template v-slot:header>
    <h1>Here might be a page title</h1>
 </template>

2、只有下面这一种情况:当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。 这样我们就可以把 v-slot 直接用在组件标签上。除此之外,v-slot 必须用在 <template>元素上。

<!-- 这是父组件哦-->
<div>
  <h1>hey,我是父组件Father的标题</h1>
  <Child v-slot:default="slotProps">
      {{ slotProps.childData.Name}}
      {{ slotProps.childData.Age}}
  </Child>
</div>

3、跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把 v-slot: 替换为字符 #。

例如 v-slot:header 可以被重写为 #header,和其它指令一样,该缩写只在其有参数的时候才可用。

<template #header>
    <h1>Here might be a page title</h1>
 </template>

也就是说,如果你希望使用缩写的话,你必须始终以明确插槽名取而代之,default不可以省略

<Child  #default="{childData}">
  {{ childData.Name }}
</Child >

或者使用不带参数的 v-slot :

<Child v-slot="{childData}">
  {{ childData.Name }}
</Child >

(2)slot & slot-scope的使用

<template>
  <div>
    <h1>这里是子组件</h1>
    <slot name="mySlot" v-bind:user="childUser"> </slot>
 </div>
</template>
<script>
  export default {
    name: 'Son',
    data() {
      return {
        childUser: { Name: "Tom", Age: 23 }
      }
    }
  }
</script>
<div>
    <h1>hey,我是父组件Father的标题</h1>
    <Son>
      <div slot="mySlot" slot-scope="data">
          {{ data.user.Name }}
          {{ data.user.Age }}
      </div>
    </Son>
  </div>

1、 slot="default" 可以省略不写,slot可以用在 <template>元素,也可以用在任意的普通元素上。

2、这里的 slot-scope 声明了被接收的 prop 对象会作为 slotProps 变量存在于 <template> 作用域中。你可以像命名 JavaScript函数参数一样随意命名 slotProps。同样的,slot-scope可以用在 template元素,也可以用在任意的普通元素上。

5、进阶

在循环中使用slot插槽时的传值方法、组件中遍历插槽

确定不点个赞再走?

有问题欢迎跟我交流,简书不常登陆,我的CSDN地址:
https://blog.csdn.net/qq_37024887?type=blog

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容

  • 什么是组件? 组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装...
    youins阅读 9,478评论 0 13
  • 【2019-3-4更新】Vue 2.6+修改了部分语法,对插槽的使用有了较多的更新。在本文中笔者在相应位置给出了更...
    果汁凉茶丶阅读 10,244评论 2 36
  • 最近忙着写一些组件,关于插槽这一块自己还是用着 slot 和 slot-scope,然后看了一下文档的更新,于是又...
    费莱姆阅读 931评论 0 7
  • 传递静态或动态Prop 传入静态的值: 这时候值是一个字符串你也可以通过v-bind动态赋值: 这时候值是一个js...
    A郑家庆阅读 380评论 0 0
  • 什么插槽 官网说明: 在 2.6.0中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指...
    全球顶尖伪极客阅读 2,253评论 0 0