和 HTML 元素一样,我们经常需要向一个组件传递内容,像这样:
说白话就是可以像以前一样在html部分写htm的代码,然后通过插槽将其带进组件中的HTML中

  1. <my-cmp>
  2. Something bad happened.
  3. </my-cmp>

如果有这样的需求,我们就可以通过插槽来做。

插槽内容

通过插槽,我们可以这样合成组件:

  1. <my-cmp>
  2. 写在组件标签结构中的内容
  3. </my-cmp>

组件模板中可以写成:

  1. <div>
  2. <slot></slot>
  3. </div>

当组件渲染时,**<slot></slot>**将会被替换为“写在组件标签结构中的内容”。
插槽内可以包含任何模板代码,包括HTML和其他组件。
如果**<my-cmp>没有包含<slot>**元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。
示例

  1. <section id="app">
  2. <slot-div>
  3. <a href="">我是插槽</a>
  4. <br>
  5. 我是插槽
  6. </slot-div>
  7. </section>
  8. <script>
  9. Vue.component('slot-div',{
  10. template:`
  11. <div>
  12. <slot>子组件中的内容,我不会显示,因为我是默认值,不传递数据我才会显示</slot>
  13. </div> `
  14. })
  15. const vm = new Vue({
  16. el: "#app",
  17. data: {
  18. }
  19. });
  20. </script>

效果图
image.png

编译作用域

当在插槽中使用数据时:

  1. <my-cmp>
  2. 这是插槽中使用的数据:{{ user }}
  3. </my-cmp>

该插槽跟模板的其他地方一样可以访问相同的实例属性,也就是相同的“作用域”,而不能访问<my-cmp>的作用域。
请记住:
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
示例

  1. <section id="app">
  2. <slot-div>
  3. 我是插槽:slot-div 我的父组件是vm ,输出信息为:{{ name }}
  4. </slot-div>
  5. </section>
  6. <script>
  7. Vue.component('slot-button',{
  8. data(){
  9. return {
  10. name:'子组件:slot-button'
  11. }
  12. },
  13. template:`
  14. <div>
  15. <slot></slot>
  16. </div>
  17. `
  18. })
  19. Vue.component('slot-div',{
  20. data(){
  21. return {
  22. name:'子组件:slot-div'
  23. }
  24. },
  25. template:`
  26. <div>
  27. <slot></slot>
  28. <slot-button>
  29. 我是插槽:slot-button 我的父组件是slot-div 输出信息为:{{name}}
  30. </slot-button>
  31. </div> `
  32. })
  33. const vm = new Vue({
  34. el: "#app",
  35. data: {
  36. name:'父组件'
  37. }
  38. });
  39. </script>

效果图
image.png

后备内容

我们可以设置默认插槽,它会在没有提供内容时被渲染,如,在<my-cmp>组件中:

  1. Vue.compopnent('my-cmp', {
  2. template: `
  3. <button type="submit">
  4. <slot></slot>
  5. </button>
  6. `
  7. })

我们希望这个<button>内绝大多数情况下都渲染文本“Submit”,此时就可以将“Submit”作为后备内容,如:

  1. Vue.compopnent('my-cmp', {
  2. template: `
  3. <button type="submit">
  4. <slot>Submit</slot>
  5. </button>
  6. `
  7. })

当使用组件未提供插槽时,后备内容将会被渲染。如果提供插槽,则后备内容将会被取代。说白就是插槽默认显示数据

具名插槽

当有多个插槽时,又需要划分区域时可以使用具名插槽
有时我们需要多个插槽,如<my-cmp>组件:

  1. Vue.compopnent('my-cmp', {
  2. template: `
  3. <div class="container">
  4. <header>
  5. <!-- 页头 -->
  6. </header>
  7. <main>
  8. <!-- 主要内容 -->
  9. </main>
  10. <footer>
  11. <!-- 页脚 -->
  12. </footer>
  13. </div>
  14. `
  15. })

此时,可以在<slot>元素上使用一个特殊的特性:name。利用这个特性定义额外的插槽:

  1. Vue.compopnent('my-cmp', {
  2. template: `
  3. <div class="container">
  4. <header>
  5. <slot name="header"></slot>
  6. </header>
  7. <main>
  8. <slot></slot>
  9. </main>
  10. <footer>
  11. <slot name="footer"></slot>
  12. </footer>
  13. </div>
  14. `
  15. })

一个不带 name 的 <slot> 出口会带有隐含的名字“default”。
在向具名插槽提供内容的时候,我们可以在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:

  1. <my-cmp>
  2. <template v-slot:header>
  3. <h1>头部</h1>
  4. </template>
  5. <p>内容</p>
  6. <p>内容</p>
  7. <template v-slot:footer>
  8. <p>底部</p>
  9. </template>
  10. </my-cmp>

现在<template>元素中的所有内容都会被传入相应的插槽。任何没有被包裹在带有v-slot<template>中的内容都会被视为默认插槽的内容。
为了模板更清晰,也可以写成以下这样:

  1. <my-cmp>
  2. <template v-slot:header>
  3. <h1>头部</h1>
  4. </template>
  5. <template v-slot:default>
  6. <p>内容</p>
  7. <p>内容</p>
  8. </template>
  9. <template v-slot:footer>
  10. <p>底部</p>
  11. </template>
  12. </my-cmp>

注意:v-slot只能添加在<template>上,只有一种例外情况。

具名插槽的缩写

Vue 2.6.0新增

v-onv-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header
示例

  1. <section id="app">
  2. <slot-div>
  3. <template #header>
  4. <p>我是头部</p>
  5. </template>
  6. <template #default>
  7. <p>我是内容1</p>
  8. <p>我是内容2</p>
  9. </template>
  10. <template #footer>
  11. <p>我是底部</p>
  12. </template>
  13. </slot-div>
  14. </section>
  15. <script>
  16. Vue.component('slot-div',{
  17. template:`
  18. <div class="container">
  19. <header>
  20. <slot name='header'>默认值:我是header区域的包含块</slot>
  21. </header>
  22. <main>
  23. <slot>默认值:我是main区域的包含块</slot>
  24. </main>
  25. <footer>
  26. <slot name='footer'>默认值:我是footer区域的包含块</slot>
  27. </footer>
  28. </div>
  29. `
  30. })
  31. const vm = new Vue({
  32. el: "#app",
  33. data: {
  34. }
  35. });
  36. </script>

效果图
image.png

作用域插槽

访问子组件的数据
为了能够让插槽内容访问子组件的数据,我们可以将子组件的数据作为<slot>元素的一个特性绑定上去:

  1. Vue.component('my-cmp', {
  2. data () {
  3. return {
  4. user: {
  5. name: '杉杉',
  6. age: 18,
  7. }
  8. }
  9. },
  10. template: `
  11. <span>
  12. <slot v-bind:user="user"></slot>
  13. </span>
  14. `,
  15. })

绑定在 <slot> 元素上的特性被称为插槽 prop
那么在父级作用域中,我们可以给v-slot带一个值来定义我们提供的插槽prop的名字:

  1. <div id="app">
  2. <my-cmp>
  3. <template v-slot:default="slotProps">
  4. {{ slotProps.user.name }}
  5. </template>
  6. </my-cmp>
  7. </div>

独占默认插槽的缩写语法

当被提供的内容只有默认插槽时,组件的标签可以被当作插槽的模板来使用,此时,可以将v-slot直接用在组件上:

  1. <my-cmp v-slot:default="slotProps">
  2. {{ slotProps.user.name }}
  3. </my-cmp>

也可以更简单:

  1. <my-cmp v-slot="slotProps">
  2. {{ slotProps.user.name }}
  3. </my-cmp>

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

  1. <!-- 无效,会导致警告 -->
  2. <my-cmp v-slot="slotProps">
  3. {{ slotProps.user.name }}
  4. <template v-slot:other="otherSlotProps">
  5. slotProps 在这里是不合法的
  6. </template>
  7. </my-cmp>

只要出现多个插槽,就需要为所有的插槽使用完整的基于<template>的语法。

解构插槽Prop

我们可以使用解构传入具体的插槽prop,如:

  1. <my-cmp v-slot="{ user }">
  2. {{ user.name }}
  3. </my-cmp>

这样模板会更简洁,尤其是在为插槽提供了多个prop时。
此外还可以有其他可能,如prop重命名:

  1. <my-cmp v-slot="{ user: person }">
  2. {{ person.name }}
  3. </my-cmp>

以及自定义后备内容,当插槽prop是undefined时生效:

  1. <my-cmp v-slot="{ user = { name: 'Guest' } }">
  2. {{ user.name }}
  3. </my-cmp>

示例

  1. <section id="app">
  2. <slot-div>
  3. <template v-slot='slotProps'>
  4. 我是插槽内容 {{ slotProps.user.name}}
  5. </template>
  6. <br>
  7. </slot-div>
  8. </section>
  9. <script>
  10. Vue.component('slot-div', {
  11. data() {
  12. return {
  13. user: {
  14. name: '李四',
  15. age: 12,
  16. },
  17. }
  18. },
  19. template: `
  20. <div>
  21. <slot v-bind:user=user></slot>
  22. </div> `
  23. })
  24. const vm = new Vue({
  25. el: "#app",
  26. data: {
  27. user: {
  28. name: '张三',
  29. age: 10
  30. },
  31. }
  32. });
  33. </script>

动态插槽名

Vue 2.6.0新增

动态填写插槽名

  1. <my-cmp>
  2. <template v-slot:[dynamicSlotName]>
  3. ...
  4. </template>
  5. </my-cmp>

示例

  1. <section id="app">
  2. <!-- 动态特姓名 -->
  3. <div v-bind:[user]='user.name="asd"'>
  4. {{user.name}}
  5. </div>
  6. <slot-div>
  7. <!-- 动态插槽明 -->
  8. <template v-slot:[main]>
  9. 我是动态插槽
  10. </template>
  11. <!-- 下方为简写 -->
  12. <!-- <template #[main]>
  13. 我是动态插槽
  14. </template> -->
  15. </slot-div>
  16. </section>
  17. <script>
  18. Vue.component('slot-div',{
  19. template:`
  20. <div>
  21. <slot name='main'></slot>
  22. </div>
  23. `
  24. })
  25. const vm = new Vue({
  26. el: "#app",
  27. data: {
  28. user: {
  29. name: '张三',
  30. age: 10
  31. },
  32. // 插槽名字的数据存放处
  33. main:'main'
  34. }
  35. });
  36. </script>

废弃了的语法

带有slot特性的具名插槽

自 2.6.0 起被废弃

  1. <my-cmp>
  2. <template slot="header">
  3. <h1>头部</h1>
  4. </template>
  5. <template>
  6. <p>内容</p>
  7. <p>内容</p>
  8. </template>
  9. <template slot="footer">
  10. <p>底部</p>
  11. </template>
  12. </my-cmp>

带有slot-scope特性的作用域插槽

自 2.6.0 起被废弃

  1. <my-cmp>
  2. <template slot="default" slot-scope="slotProps">
  3. {{ slotProps.user.name }}
  4. </template>
  5. </my-cmp>