彻底搞懂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 和 <slot> 的不同:通过props属性,父组件可以向子组件传递属性,可是父组件不能通过属性传递带标签的内容、甚至是组件,而插槽可以。
2、插槽如何使用
(1)匿名插槽
匿名插槽就是没有设置name属性的插槽。
匿名插槽又叫单个插槽、默认插槽。
可以放置在组件的任意位置。
一个组件中只能有一个该类插槽。
作为找不到匹配的内容片段时的备用插槽。
匿名插槽只能作为没有slot属性的元素的插槽。
例子:
<div class="child">
<p>子组件</p>
<slot name="body">主体默认值</slot>
<slot>这是个匿名插槽(没有name属性),这串字符是匿名插槽的默认值。</slot>
</div>
<div class="parent">
<p>父组件</p>
<child>
<p slot=body">我是主体</p>
<p>我是其他内容</p>
<p slot="footer">我是尾巴</p>
</child>
</div>
运行效果:
父组件
子组件
我是主体
我是其他内容
<p slot="body"> ——插入<slot name="body">中,并且覆盖掉主体默认值。
<p>我是其他内容</p> ——插入默认的<slot>中,并且覆盖掉匿名插槽的默认值。
<p slot="footer"> ——被丢弃了,因为没有name="footer"的插槽与之匹配。
如果子组件中的匿名插槽不存在,则<p>我是其他内容</p>也会被丢弃。
注意:默认值只会在没有提供内容的时候被渲染。例如在一个 <submit-button> 组件中:
(2)具名插槽
意思就是具有名字的插槽,名字通过属性:name来定义。
具名插槽可以在一个组件中出现N次。出现在不同的位置。
<!-- <base-layout>组件-->
<div class="container">
<header>
<!-- 我们希望把页头放这里 -->
<slot name="header"></slot>
</header>
<main>
<!-- 我们希望把主要内容放这里 -->
<slot></slot>
</main>
<footer>
<!-- 我们希望把页脚放这里 -->
<slot name="footer"></slot>
</footer>
</div>
我们可以在一个 <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) <template v-slot:header> 插入<slot name="header"></slot>中。
(2)<p>我是main的内容111</p><p>我也是main的内容222</p> 插入默认的<slot>中。可以看到,任何没有被包裹在带有 v-slot
的<template>
中的内容都会被视为默认插槽的内容。
(3)<slot name="footer"></slot> 插入 <slot name="footer"></slot>中。
注意:一个不带 name 的 <slot>
插槽会带有隐含的名字“default”。如果你希望更明确一些,可以在一个 <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>
两种写法的渲染效果是一样的:
<div class="container">
<header>
<h1>我是头header</h1>
</header>
<main>
<p>我是main的内容111</p>
<p>我也是main的内容222</p>
</main>
<footer>
<p>我是footer</p>
</footer>
</div>
注意 v-slot
只能添加在 <template>
上 (只有一种例外情况),这一点和已经废弃的 slot
attribute不同。
(3)作用域插槽
编译作用域
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
我再来举个栗子:设想一个带有如下模板的 <Child> 组件:
<template>
<div>
<h1>hey,我是组件Child的标题</h1>
<slot></slot>
</div>
</template>
<script>
export default {
data() {
return{
childUser:{Name:"Tom",Age:23}
}
}
</script>
当我使用Child组件时,我想访问Child中的数据childUser并且将其展示在插槽的位置:
<!-- 这是父组件哦-->
<div>
<h1>hey,我是父组件Father的标题</h1>
<Child>
{{childUser.Name}},
{{childUser.Age}}
</Child>
</div>
然而上述代码不会正常工作,因为只有 <Child> 组件可以访问到 childUser,而我们提供的内容【{{childUser.Name}}, {{childUser.Age}}】是在父级渲染的。
为了让 childUser在父级的插槽内容中可用,我们可以将 childUser作为 <slot> 元素的一个属性绑定上去:
<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 的名字:
<!-- 这是父组件哦-->
<div>
<h1>hey,我是父组件Father的标题</h1>
<Child>
<template v-slot:default="slotProps">
{{ slotProps.childData.Name}}
{{ slotProps.childData.Age}}
</template>
</Child>
</div>
在这个例子中,我们将包含 [ 所有插槽 prop 的对象 ] 命名为 slotProps,但你也可以使用任意你喜欢的名字。
在上述情况下,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot 直接用在组件上:
<Child v-slot:default="slotProps">
{{ slotProps.childData.Name }}
</Child>
这种写法还可以更简单。就像假定未指明的内容对应默认插槽一样,不带参数的 v-slot 被假定对应默认插槽:
<Child v-slot="slotProps">
{{ slotProps.childData.Name }}
</Child>
注意默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确:
<!-- 无效,会导致警告 -->
<Child v-slot="slotProps">
{{ slotProps.childData.Name }}
<template v-slot:other="otherSlotProps">
slotProps is NOT available here
</template>
</Child >
只要出现多个插槽,请始终为所有的插槽使用完整的基于 <template> 的语法:
<Child >
<template v-slot:default="slotProps">
{{ slotProps.childData.Name }}
</template>
<template v-slot:other="otherSlotProps">
...
</template>
</Child>
解构插槽 Prop
作用域插槽的内部工作原理是将你的插槽内容包括在一个传入单个参数的函数里,所以,这意味着 v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式。
<Child v-slot="{ childData}">
{{ childData.Name }}
</Child>
这样可以使模板更简洁,尤其是在该插槽提供了多个 prop 的时候。它同样开启了 prop 重命名等其它可能,例如将 childData重命名为 person:
<Child v-slot="{ childData: person }">
{{ person.Name }}
</Child >
你甚至可以定义默认内容,用于插槽 prop 是 undefined 的情形:
<Child v-slot="{ childData= { Name: 'Guest' } }">
{{ childData.Name }}
</Child >
3、v-slot、slot、slot-scope
slot、slot-scope已经被废弃。推荐使用vue2.6.0中的v-slot。
· slot 的使用(匿名插槽&具名插槽)
<div class="child">
<p>子组件</p>
<slot name="body">具名插槽的默认内容。</slot>
<slot>这是个匿名插槽(没有name属性),这串字符是匿名插槽的默认值。</slot>
</div>
<div class="parent">
<p>父组件</p>
<child>
<p slot="body">我是主体</p>
<p>我是其他内容</p>
</child>
</div>
运行效果:
父组件
子组件
我是主体
我是其他内容
· v-slot 的使用
v-slot
指令自 Vue 2.6.0 起被引入,提供更好的支持 slot 和 slot-scope 的 API 替代方案。
在一个 <template>
元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称。
匿名插槽&具名插槽&作用域插槽看上面的例子
跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把 v-slot: 替换为字符 #。
例如 v-slot:header 可以被重写为 #header,和其它指令一样,该缩写只在其有参数的时候才可用。
如果你希望使用缩写的话,你必须始终以明确插槽名取而代之,default不可以省略:
<Child #default="{ childData}">
{{ childData.Name }}
</Child >
· slot-scope的使用
<div class="child">
<div>
<slot name="default" :msg="msg"> </slot>
<p>这里是child 组件</p>
</div>
</div>
<child >
<div slot="default" slot-scope="childData">//作用域插槽的用法(slot-scope)
{{ childData.msg }}
</div>
</child >