<div id="app">
<my-com value="注册"></my-com>
<my-com value="提交"></my-com>
<my-com value="重置"></my-com>
</div>
<template id="mycom">
<button>{
{
value}}</button>
</template>
<script>
let MyCom = ({
props:["value"],
template:"#mycom",
})
let vm = new Vue({
el:"#app",
components:{
MyCom,
}
})
</script>
此时我们在调用子组件的时候,设置不同的 value
值就会有不同的按钮.
在页面中的显示结果如下图所示:
上面的方法虽然也可以满足我们的需求,但其实通过我们学习html
,我们其实更习惯下面的这种写法:
<my-com>注册</my-com>
<my-com>提交</my-com>
<my-com>重置</my-com>
想要这么写,我们就必须使用到 slot
插槽.在此之前,我们先来看一下编译作用域
通过前面的学习,其实我们已经了解到父子组件都有自己不同的模板和独立的作用域,每一个组件在实例化的时候作用域都是独立的.
那么我们动一动我们可爱的小脑瓜,就会想到假如我们在父组件中使用子组件时,子组件中嵌套的内容会在哪个作用域里编译呢??
那我们就来看看下面的例子:
<div id="app">
<my-com>
{
{
msg}}
</my-com>
</div>
<template id="mycom">
<button>{
{
msg}}</button>
</template>
<script>
let MyCom = ({
data(){
return{
msg:"hello world"
}
},
template:"#mycom",
})
let vm = new Vue({
el:"#app",
data:{
msg:"你好,世界"
},
components:{
MyCom,
}
})
</script>
此时组件中嵌套的 msg
究竟是在父组件中的作用域中编译还是在子组件作用域编译呢?这涉及到 msg
使用的是父组件的数据还是子组件的数据.
答案当然就是父组件中的数据了,但是现在我们还没有办法演示这段代码的结果,等后面我们介绍完插槽,再来看看这里的结果会是怎样.
组件作用域简单的来说就是:
- 父组件模板的内容在父组件中编译
- 子组件模板中的内容在子组件中编译
所以就会有一个比较常见的错误,父组件模板内使用内将一个指令绑定到子组件内部的属性/方法
示例代码如下:
<div id="app">
<my-com v-show = "isShow"></my-com>
</div>
<template id="mycom">
<button>按钮</button>
</template>
<script>
let MyCom = ({
data(){
return{
isShow:true,
}
},
template:"#mycom",
})
let vm = new Vue({
el:"#app",
components:{
MyCom,
}
})
</script>
报错信息如下图:
这样做会报错的原因是: isShow
是我们定义在子组件中的数据,父组件中并不能直接找到子组件的数据.
如果需要绑定到子组件作用域内的数据,就可以将指令绑定在子组件的根节点上.
如下:
<div id="app">
<my-com></my-com>
</div>
<template id="mycom">
<button v-show = "isShow">按钮</button>
</template>
<script>
let MyCom = ({
data(){
return{
isShow:false,
}
},
template:"#mycom",
})
let vm = new Vue({
el:"#app",
components:{
MyCom,
}
})
</script>
这样做就不会报错.
自己可以慢慢体会一下这两种写法,通过上面的例子,我们就可以对 编译作用域 有一点点的了解.
那么想想我们开篇提出的问题,在父组件模板中使用子组件时,子组件中嵌套的内容是属于父组件的编译,但是我们希望这个内容能在子组件中使用,此时我们就需要一种方式来混合父组件的内容与子组件自己的模板,这个过程被称为内容分发.
原来我们在组件里面是不能写内容的,因为写了之后也会被模板整体替换,现在有了插槽之后,我们就可以在组件里定义内容了.
还是上面的例子,我们子组件中嵌套的文本在子组件模板中使用,我们就需要在子组件模板中使用插槽
<div id="app">
<my-com>按钮</my-com>
<my-com>{
{
value}}</my-com>
</div>
<template id="mycom">
<button >
<slot></slot>
</button>
</template>
<script>
let MyCom = ({
template:"#mycom",
})
let vm = new Vue({
el:"#app",
data:{
value:"注册"
},
components:{
MyCom,
}
})
</script>
使用插槽之后的结果:
通过这种方法,我们就可以很方便的在父组件中,通过slot插槽向子组件中分发内容
那么我们想想,既然可以插入文本内容,那是不是也可以在里面插入 DOM
标签呢?
示例代码如下:
<div id="app">
<my-com>
<h3>你好啊!我是依古比古</h3>
</my-com>
<my-com>
<h2>你好,我是玛卡巴卡</h2>
</my-com>
</div>
<template id="mycom">
<div>
<slot></slot>
</div>
</template>
<script>
let MyCom = ({
template:"#mycom",
})
let vm = new Vue({
el:"#app",
components:{
MyCom,
}
})
</script>
结果如下图:
通过上面的这段代码,我们发现插槽不仅仅可以插入文本内容,还可以插入 DOM
标签.
插槽的更多作用我们往下面继续看.
我们并不是每一次使用子组件的时候都会给里面插入内容,如果我们不给里面插入内容,那么里面将会是什么也没有,所以我们需要设置默认内容.
如果我们需要在使用子组件时未插入内容时. 显示一段默认的内容,我们就可以将默认的内容嵌套在slot 标签中
这就是插槽的备用内容, 备用内容在子组件的作用域内编译. 并且只有在宿主元素为空,且没有要插入的内容时才显示备用内容。
还是使用按钮的那个例子,看下面的代码:
<div id="app">
<my-com></my-com>
<my-com>
{
{
value}}
</my-com>
</div>
<template id="mycom">
<button>
<slot>我是默认内容</slot>
</button>
</template>
<script>
let MyCom = ({
template:"#mycom",
})
let vm = new Vue({
el:"#app",
data:{
value:"注册"
},
components:{
MyCom,
}
})
</script>
显示的结果如下图:
这样做就算子组件没有给我们分发内容,我们也可以使用默认内容,不至于让显示的内容为空.
在我们使用组件的时候,有时候需要在不同的位置插入不同的内容,只有一个插槽很可能不能满足我们的需求,看示例:
我们的需求是:
示例代码如下:
<div id="app">
<my-com>
<h2>标题~~标题</h2>
<p>希望第二天的我</p>
<p>仍然对不同充满宽容</p>
<p>继续对未知饱含敬畏</p>
<span>2020年6月12号</span>
</my-com>
</div>
<template id="mycom">
<div class = "article">
<div class="title">
<slot></slot>
</div>
<div class="content">
<slot></slot>
</div>
<div class="time">
<slot></slot>
</div>
</div>
</template>
<script>
let MyCom = ({
template:"#mycom",
})
let vm = new Vue({
el:"#app",
data:{
value:"注册"
},
components:{
MyCom,
}
})
</script>
这个时候我们可以看到,我们要插入的内容被重复插入了三次,此时每一个插槽都插入了所有的内容,不符合我们的预期.
那么我们要怎样才能将分发的内容制定到具体的插槽上呢???
这个时候就需要具名插槽
元素有一个特殊的 name
属性, 使用 name
来进一步配置如何分发内容,每个插槽都可以有具体的名字.具名插槽将匹配内容片段中有对应的 slot
元素.
没有使用 name
属性的 slot
插槽被称为 匿名插槽,也可以称之为 默认插槽,我们在子组件中仍然可以有一个额匿名插槽,作为找不到匹配内容片段的备用插槽,若果没有默认插槽,这些找不到匹配的内容片段就会被抛弃.
看下面的示例代码:
<div id="app">
<my-com>
<h2 slot = "title">标题~~标题</h2>
<p>希望第二天的我</p>
<p>仍然对不同充满宽容</p>
<p>继续对未知饱含敬畏</p>
<span slot = "time">2020年6月12号</span>
</my-com>
</div>
<template id="mycom">
<div class = "article">
<div class="title">
<slot name = "title"></slot>
</div>
<div class="content">
<slot >这里是默认内容</slot>
</div>
<div class="time">
<slot name = "time"></slot>
</div>
</div>
</template>
<script>
let MyCom = ({
template:"#mycom",
})
let vm = new Vue({
el:"#app",
components:{
MyCom,
}
})
</script>
可以看到,完全就是我们想要的效果.分发的内容都正确的插到了插槽上面.
过上面的例子我们就知道了,slot
如果没有显示的使用 name
属性指定插槽的名字,那么slot
默认有个名字default
,默认插槽,如果在分发内容时,没有指定插槽,所有的内容都将默认插到默认插槽上.
通过前面的学习,我们已经知道,插槽的内容是最后在子组件模板上渲染的,那么就会在有些时候在分发内容中使用子组件中才会有的数据,这样的话我们该怎么办呢???
这个时候就要用到作用域插槽了.
作用域插槽是一种特殊类型的插槽,用作一个(能被传递数据的)可重用模板来代替已经渲染好的元素.
简而言之,就是利用slot 标签将子组件的数据传递到分发内中上,就像prop传递数据给组件一样
在父级中,具有特殊特性 slot-scope
的 元素必须存在,表示它是作用域插槽的模板。
slot-scope
的值将被用作一个临时变量名,此变量接收从子组件传递过来的 props
对象:
当我们使用作用域插槽后,slot-scope
属性接受的 props
是一个对象,我们来验证一下:
<my-com>
<template slot-scope="props">
{
{
props}}
</template>
</my-com>
页面的显示结果如下图:
下面我们就来看看作用域插槽的使用案例:
<div id="app">
<my-com>
<!-- slot-scope 不能直接在组件上使用,只能在DOM元素上使用-->
<tempalte slot-scope="props">
{
{
props.msg}}
{
{
props.value}}
</tempalte>
</my-com>
</div>
<template id="mycom">
<div>
<slot :msg = "msg" :value="value"></slot>
</div>
</template>
<script>
let MyCom = ({
template:"#mycom",
data(){
return{
msg:"今天天气真不错",
value:"你的衣服真好看"
}
}
})
let vm = new Vue({
el:"#app",
components:{
MyCom,
}
})
在 2.5.0+,slot-scope 能被用在任意元素或组件中而不再局限于
。
也就是说我们还能像下面这样写:
<my-com>
<div slot-scope = "props">
<h2>{
{
props.msg}}</h2>
<h3>{
{
props.value}}</h3>
</div>
</my-com>
既然我们前面已经提到了props
中接收的数据是对象,那么我们就可以通过解构来使用数据,如下:
<my-com>
<div slot-scope = "{msg,value}">
<h2>{
{
msg}}</h2>
<h3>{
{
value}}</h3>
</div>
</my-com>
页面中的结果如下:
但是非常遗憾的是,作用域插槽和具名插槽的用法会在不久的将来被废弃.
那么之前我们提到的问题难道就没有办法解决了吗?
当然不是的,我们需要继续往下看.
在 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。它取代了 slot 和 slot-scope 这两个目前已被废弃但未被移除且仍在文档中的 attribute。
Vue
实现了一套内容分发的 API
,将
元素作为承载分发内容的出口。
在向具名插槽提供内容的时候,我们可以在一个 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称.
继续使用之前文章的案例:
<div id="app">
<my-com>
<h2 v-slot:title>标题~~标题</h2>
<p>希望第二天的我</p>
<p>仍然对不同充满宽容</p>
<p>继续对未知饱含敬畏</p>
<span v-slot:time>2020年6月12号</span>
</my-com>
</div>
<template id="mycom">
<div class = "article">
<div class="title">
<slot name = "title"></slot>
</div>
<div class="content">
<slot >这里是默认内容</slot>
</div>
<div class="time">
<slot name = "time"></slot>
</div>
</div>
</template>
<script>
let MyCom = ({
template:"#mycom",
})
let vm = new Vue({
el:"#app",
components:{
MyCom,
}
})
</script>
结果如下:
现在元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有
v-slot
的 中的内容都会被视为默认插槽的内容。
如果你希望更明确一些,仍然可以在一个 中包裹默认插槽的内容:
<template v-slot:default>
</template>
绑定在
元素上的 attribute
被称为插槽 prop
。现在在父级作用域中,我们可以使用带值的v-slot
来定义我们提供的插槽 prop
的名字:
<div id="app">
<!-- 使用组件 -->
<my-child >
<template v-slot:default="props">
<button>{
{
props.text }}</button>
</template>
</my-child>
</div>
<!-- 组件模板 -->
<template id="mychild">
<div>
<slot :text="text"></slot>
</div>
</template>
<script>
// 组件选项对象
let MyChild = {
template: `#mychild`,
data(){
return {
text: "提交"
}
}
};
// 实例中注册组件
const vm = new Vue({
el:"#app",
components: {
MyChild
}
})
</script>
在上述情况下,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot
直接用在组件上:
<div id="app">
<!-- 使用组件 -->
<my-child v-slot:default="props">
<button>{
{
props.text }}</button>
</my-child>
</div>
这种写法还可以更简单。就像假定未指明的内容对应默认插槽一样,不带参数的 v-slot 被假定对应默认插槽:
<div id="app">
<!-- 使用组件 -->
<my-child v-slot="props">
<button>{
{
props.text }}</button>
</my-child>
</div>
这用这种简单语法的情况就是在组件中只有一个默认插槽,一但有多个插槽,请使用完整的语法
动态参数也可以用在v-slot
上,用来定义动态的插槽名.
示例:
<div id="app">
<!-- 使用组件 -->
<my-child >
<template v-slot:[head]>
<h2>这是一篇介绍vue插槽的文章</h2>
</template>
<p>这是文章的第一段</p>
<p>这是文章的第二段内容</p>
<p>这是文章的第三段内容</p>
<template v-slot:[food]>
<span>2020年5月1日</span>
</template>
</my-child>
</div>
<!-- 组件模板 -->
<template id="mychild">
<div class="article">
<div class="title">
<slot name="title">这里是标题内容的插槽</slot>
</div>
<div class="contont">
<slot>这里是默认插槽</slot>
</div>
<div class="time">
<slot name="time">这里是时间的插槽</slot>
</div>
</div>
</template>
<script>
// 组件选项对象
let MyChild = {
template: `#mychild`
};
// 实例中注册组件
const vm = new Vue({
el:"#app",
data:{
head:"title",
food:"time"
},
components: {
MyChild
}
})
</script>
跟v-on
和 v-bind
一样,v-slot
也有缩写,即把参数之前的所有内容 (v-slot:
) 替换为字符 #
。例如 v-slot:header
可以被重写为 #header
:
<div id="app">
<!-- 使用组件 -->
<my-child >
<template #title>
<h2>这是一篇介绍vue插槽的文章</h2>
</template>
<p>这是文章的第一段</p>
<p>这是文章的第二段内容</p>
<p>这是文章的第三段内容</p>
<template #time>
<span>2020年5月1日</span>
</template>
</my-child>
</div>
然而,和其它指令一样,该缩写只在其有参数的时候才可用。这意味着以下语法是无效的:
<my-child >
<!-- 这种写法无效 -->
<template #="props">
<h2>这是一篇介绍vue插槽的文章</h2>
</template>
</my-child>
如果你希望使用缩写的话,你必须始终以明确插槽名取而代之:
<my-child >
<!-- 这种写法有效,因为有指令参数 -->
<template #deatule="props">
<h2>这是一篇介绍vue插槽的文章</h2>
</template>
</my-child>