含义:
插槽就是子组件中的提供给父组件使用的一个占位符,用slot标签表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的slot标签。
例如:
// 子组件定义slot占位符
<template>
<h1>子组件</h1>
<slot>submit</slot>
</template>
// 父组件填充插槽内容,然后就会取代子组件中的slot占位符
<template>
<child>
<div style="color:red">父组件填充插槽内容,然后就会取代子组件中的slot占位符</div>
</child>
</template>
<script>
import Child from './components/child.vue'
export default {
components: { Child }
}
</script>
效果图:
如果在父组件不填充插槽内容,默认显示slot标签之中的内容:
<!--子组件-->
<template>
<h1>子组件</h1>
<slot><div style="color:blue">submit</div></slot>
</template>
<!--父组件-->
<template>
<child>
<!-- child中间为空白,即没填充插槽内容 -->
<!-- <div style="color:red">父组件填充插槽内容</div> -->
</child>
</template>
<script>
import Child from './components/child.vue'
export default {
components: { Child }
}
</script>
效果图:
注意:如果子组件的 template 中没有包含一个slot标签,则该组件起始标签和结束标签之间的任何内容都会被抛弃。
// 子组件没有<slot>
<template>
<h1>子组件</h1>
<!-- 把slot元素删除掉,就没有占位符了,其他组件只能调用本组件,不能往本组件添加元素 -->
<!-- <slot>submit</slot> -->
</template>
// 父组件调用的child标签中间的所有内容都会被抛弃
<template>
<child>
<!-- 下面一行代码不会渲染,被抛弃了,因为子组件中没有slot元素 -->
<div style="color:red">父组件填充插槽内容,然后就会取代子组件中的slot占位符</div>
</child>
</template>
<script>
import Child from './components/child.vue'
export default {
components: { Child }
}
</script>
效果图:
具名插槽:
含义:具名插槽其实就是给插槽取个名字。一个子组件可以放多个插槽,而且可以放在不同的地方,而父组件填充内容时,可以根据这个名字把内容填充到对应插槽中。
子组件:
<template>
<header>
<!-- 我们希望把页头放这里 -->
<slot name="header"></slot>
</header>
<main>
<!-- 我们希望把主要内容放这里,一个不带 name 的 <slot> 出口会带有隐含的名字“default” -->
<!-- 这个无名插槽,父组件中任何没有被包裹在带有 v-slot 的 <template> 中的内容都会被视为默认(default)插槽的内容 -->
<slot></slot>
</main>
<footer>
<!-- 我们希望把页脚放这里 -->
<slot name="footer"></slot>
</footer>
</template>
父组件:
<template>
<child>
<template v-slot:header>
<h1>页头内容</h1>
</template>
<p>现在template元素中的所有内容都将会被传入相应的插槽</p>
<p>任何没有被包裹在带有 v-slot 的template中的内容都会被视为默认插槽的内容</p>
<template v-slot:footer>
<p>页尾内容</p>
</template>
</child>
</template>
<script>
import Child from './components/child.vue'
export default {
components: { Child }
}
</script>
效果图:
slot元素有一个特殊的 attribute:name。这个 attribute 可以用来定义额外的插槽。一个不带 name 的 slot出口会带有隐含的名字“default”。
在向具名插槽提供内容的时候,我们可以在一个template元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称。v-slot:指令可以缩写为#。
现在template元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有 v-slot 的template中的内容都会被视为默认插槽的内容。
然而,如果你希望更明确一些,仍然可以在一个template中包裹默认插槽的内容,例如父组件这样写:
<template v-slot:default>
<p>现在template元素中的所有内容都将会被传入相应的插槽</p>
<p>任何没有被包裹在带有 v-slot 的template中的内容都会被视为默认插槽的内容</p>
</template>
效果跟上面是一样的
注意:v-slot 只能添加在template标签上 (只有一种例外情况),这一点和已经废弃的 slot attribute 不同。
作用域插槽:
问题:有时让插槽内容能够访问子组件中才有的数据是很有用的。
子组件:
<template>
<div class="slot">
<slot>{{user.lastName}}</slot>
</div>
</template>
<script>
export default {
data () {
return {
user: {
firstName: 'xiaobu',
lastName: 'lv'
}
}
}
}
</script>
<style scoped>
.slot {
color: blue
}
</style>
父组件:
<template>
<div class="slot">
<h2>父组件</h2>
<slot-child>
<template #default>
<h2>我根本获取不到子组件的内容:{{user.firstName}}</h2>
</template>
</slot-child>
</div>
</template>
<script>
import SlotChild from '../components/SlotChild.vue'
export default {
components: {
SlotChild
}
}
</script>
页面直接打不开了,控制台报错:TypeError: Cannot read properties of undefined (reading ‘firstName’)
上述代码不会正常工作,因为只有SlotChild组件可以访问到 user,而我们提供的内容是在父级渲染的,插槽真实填充的内容无法访问到子组件的状态。为了解决这个问题,作用域插槽应运而生。
在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。要做到这一点,我们需要一种方法来让子组件在渲染时将一部分数据提供给插槽。
为了让 user 在父级的插槽内容中可用,我们可以像对组件传递 prop 那样,将 user 作为slot元素的一个 attribute 绑定上去:
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
绑定在slot元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:
<slot-child>
<template v-slot:default="slotProps">
<h2>现在,我能获取到子组件的内容:{{slotProps.user.firstName}}</h2>
</template>
<!-- 或者简写为v-slot=,因为默认插槽名称就是default -->
<!-- <template v-slot="slotProps">
<h2>现在,我能获取到子组件的内容:{{slotProps.user.firstName}}</h2>
</template> -->
</slot-child>
v-slot=相当于给插槽定义我们提供的插槽 prop 的名字,然后使用slotProps.user就可以获取到属性名为user的插槽 prop ,然后slot的父组件就可以获取到子组件中的数据了。
作用域插槽和具名插槽结合使用如下:
子组件:
<div class="slot">
<slot name="slotChild" :user="user">{{user.lastName}}</slot>
</div>
父组件:
<slot-child>
<template #slotChild="slotProps">
<h2>现在,我能获取到子组件的内容:{{slotProps.user.firstName}}</h2>
</template>
<!-- 不能分开两个v-slot写成下面这样,否则会报错 -->
<!-- <template #slotChild v-slot="slotProps"">
<h2>现在,我能获取到子组件的内容:{{slotProps.user.firstName}}</h2>
</template> -->
</slot-child>
效果图: