组件的插槽
组件本身是一个容器,也是一个vue对象(data,methods…),也是一个虚拟DOM(html标签,但是这些html标签不是既定一些html标签,而是我们创建的所以也叫虚拟标签)
1、普通插槽
组件本身是一个容器,这个容器本身是空的,当我峨嵋你把需要封装的html标签装进这个组件之后,我们可以认为这个组件的内部的容积就直接塞满了,那就意味着,无法再向组件内部添加新的html结构,但是我们想在每次一个调用组件的时候,渲染一点个性化的东西进去,这个时候我们需要给组件去做一个预留空间,这个预留空间就是插槽
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<one>
<p>我是栏目一的内容</p>
<!-- 这样插入时没有效果的 -->
</one>
</div>
<template id="temp1">
<div>
<h2>我是标题</h2>
</div>
</template>
</body>
<script src="js/vue.js"></script>
<script>
var one = {
template:"#temp1"
};
new Vue({
el:"#app",
components:{
one
}
})
</script>
</html>
以上的代码在渲染的时候,我们在虚拟DOM one的里面写入了一个p标签,这就是所谓的插入,但是这个并没有效果,因为我们并没有在one组件的内部留下预留空间,也就是插槽
如果想要在组件内部预留插槽,需要使用 <slot></slot>
的标签完成
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<one>
<p>我是栏目一的内容</p>
<p>我是栏目一的内容</p>
<p>我是栏目一的内容</p>
<p>我是栏目一的内容</p>
<p>我是栏目一的内容</p>
</one>
</div>
<template id="temp1">
<div>
<h2>我是标题</h2>
<slot>
<!--
<p>我是栏目一的内容</p>
<p>我是栏目一的内容</p>
<p>我是栏目一的内容</p>
<p>我是栏目一的内容</p>
<p>我是栏目一的内容</p>
-->
</slot> <!-- 这是预留的插槽 -->
</div>
</template>
</body>
<script src="js/vue.js"></script>
<script>
var one = {
template:"#temp1"
};
new Vue({
el:"#app",
components:{
one
}
})
</script>
</html>
代码分析:
在上面的代码里面,我们在one这个虚拟标签插入了5个p标签,这个5个p标签会全部插入到slot的位置
一个插入点插入多个插槽
现在我们想让刚才的代码当中前3个p标签出现在标题的上方,后2个p标签出现在标签的下方,明显刚才的插槽肯定是不能的满足的,那么应该怎么写?
如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<one>
<p>我是栏目一的内容</p>
<p>我是栏目一的内容</p>
<p>我是栏目一的内容</p>
<p>我是栏目一的内容</p>
<p>我是栏目一的内容</p>
</one>
</div>
<template id="temp1">
<div>
<slot></slot>
<h2>我是标题</h2>
<slot>
<!--
<p>我是栏目一的内容</p>
<p>我是栏目一的内容</p>
<p>我是栏目一的内容</p>
<p>我是栏目一的内容</p>
<p>我是栏目一的内容</p>
-->
</slot> <!-- 这是预留的插槽 -->
</div>
</template>
</body>
<script src="js/vue.js"></script>
<script>
var one = {
template:"#temp1"
};
new Vue({
el:"#app",
components:{
one
}
})
</script>
</html>
代码分析:
上面的代码中,渲染的结果我们发现,我们在模板中的标题的上方也制作了一个插件slot,现在的情况,把插入的5个p标签分别插入到了标题上下两个插槽当中,从效果上来看感觉是把冲入的内容复制了一份,分别插入了不同的插槽当中,与我们想要的效果还有差距
其实我们刚才写的都是默认插槽,把这些插槽的语法写完整应该如下
<div id="app">
<one>
<p slot="default">我是栏目一的内容</p>
<p slot="default">我是栏目一的内容</p>
<p slot="default">我是栏目一的内容</p>
<p slot="default">我是栏目一的内容</p>
<p slot="default">我是栏目一的内容</p>
</one>
</div>
<template id="temp1">
<div>
<slot name="default"></slot>
<h2>我是标题</h2>
<slot name="default">
<!--
<p>我是栏目一的内容</p>
<p>我是栏目一的内容</p>
<p>我是栏目一的内容</p>
<p>我是栏目一的内容</p>
<p>我是栏目一的内容</p>
-->
</slot> <!-- 这是预留的插槽 -->
</div>
</template>
默认情况下所哟䣌slot都必须指定一个name,如果不指定则默认是default
2、具名插槽
在上面的默认插槽中,所有的slot如果不在指定name的情况下都是default,但是这个name是可以设置的,如果设置为一些其他的值,我们就把这个插槽叫做具名插槽
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<one>
<button type="button" slot="footer">我是footer</button>
<h3 slot="title">我是标题</h3>
</one>
</div>
<template id="temp1">
<div>
<slot name="title"></slot>
<p>我是一个组件</p>
<slot name="footer"></slot>
</div>
</template>
</body>
<script src="js/vue.js"></script>
<script>
var one = {
template:"#temp1"
};
new Vue({
el:"#app",
components:{
one
}
})
</script>
</html>
代码分析:
在上面的代码当中,我们可以为插槽取一个名字,从而实现一对多,或者多对一的插入,当插槽有了名字之后,我们在插入的时候可以指定插入到某一个插槽
1、我们的组件里面定义插槽的时候使用slot标签,并在这个标签上面定义一个那么属性,这个就是具名插槽
2、在调用组件的时候,可以向指定的插槽位置插入内容,只需要在这个元素上面添加 slot = “插槽名” 即可
3、具名插槽也是可以多次使用的,所以我们把上面的代码中footer改成title,这样标题标签会被插入两次
案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<app-title :show-back="true">
软帝点餐
<span slot="right-menu">+</span>
</app-title>
<!-- <app-title>
登录
<span slot="right-menu">+</span>
</app-title> -->
</div>
<template id="temp1">
<div class="page-title">
<div class="left-back" v-if="showBack">- 返回</div>
<slot></slot>
<div class="right-menu">
<slot name="right-menu"></slot>
</div>
</div>
</template>
</body>
<script src="js/vue.js"></script>
<script>
var appTitle = {
template:"#temp1",
props:{
showBack:{
type:Boolean,
default:() => false
}
}
};
new Vue({
el:"#app",
components:{
appTitle
}
})
</script>
</html>
插槽作用域
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<one>
<p>我向拿到组件内部的userName</p>
</one>
</div>
<template id="temp1">
<div class="box">
<h2>我是一个组件</h2>
<slot></slot>
</div>
</template>
</body>
<script src="js/vue.js"></script>
<script>
var one = {
template:"#temp1",
data(){
return {
userName:"zhangsan"
}
}
};
new Vue({
el:"#app",
components:{
one
}
})
</script>
</html>
我们以前一致都是外部的数据传递给组件内部,如果把组件内部的数据传递出去怎么做?
比如我要把userName的值传递到外边,我们可以在插入的标签上添加一个slot-scope=“scope”
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<one>
<div slot-scope="scope">
<p>{{scope.userName}}</p>
<p>{{scope.age}}</p>
</div>
</one>
</div>
<template id="temp1">
<div class="box">
<h2>我是一个组件</h2>
<slot :user-name="userName" :age="18"></slot>
</div>
</template>
</body>
<script src="js/vue.js"></script>
<script>
var one = {
template:"#temp1",
data(){
return {
userName:"zhangsan"
}
}
};
new Vue({
el:"#app",
components:{
one
}
})
</script>
</html>
代码分析:
通过上面的例子,我们看到,我们通过slot-scope拿到了插槽里面的作用域,然后拿到了作用域插槽上面的所有的值,然后直接使用渲染
上面的例子使用的是默认插槽,如果是具名插槽怎么办?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<one>
<div slot="footer" slot-scope="scope">
<p>{{scope.userName}}</p>
<p>{{scope.age}}</p>
</div>
</one>
</div>
<template id="temp1">
<div class="box">
<slot></slot>
<h2>我是一个组件</h2>
<slot name="footer" :user-name="userName" :age="18"></slot>
</div>
</template>
</body>
<script src="js/vue.js"></script>
<script>
var one = {
template:"#temp1",
data(){
return {
userName:"zhangsan"
}
}
};
new Vue({
el:"#app",
components:{
one
}
})
</script>
</html>
代码分析:
1、在默认插槽中,我们的数据是直接从外部传递给了userName,同时要注意默认插槽的name就是default所以可以不指定名字
2、在slot="footer"中,我们还向外传递了 :age=“18”
3、在调用组件的时候,我们可以通过slot=“default” 或者是 slot=“footer” 来决定到底插入在什么地方,同时还可以通过slot-scope拿到这个插槽的作用域(通俗点讲就是拿到这个插槽向外传递的数据)
4、这里主要作用的两个属性
slot决定插入哪个插槽
slot-scope 拿到作用域(拿到插槽上面的数据)
上面的slot-scope是过时的写法,在vue2.6.1版本开始使用新语法v-slot
旧版本语法
<div id="app">
<one>
<div slot-scope="scope">
<p>{{scope.userName}}</p>
<p>{{scope.age}}</p>
</div>
</one>
</div>
<template id="temp1">
<div class="box">
<h2>我是一个组件</h2>
<slot :user-name="userName" :age="18"></slot>
</div>
</template>
新版本语法
<one>
<template v-slot:default="scope">
<p>{{scope.userName}}</p>
<p>{{scope.age}}</p>
</template>
<template v-slot:footer="scope">
<p>{{scope.userName}}</p>
<p>{{scope.age}}</p>
</template>
</one>
代码分析:
1、v-slot是去拿作用域的,如果后面不具名旧拿的是default,所以v-slot:default 就是 v-slot
2、如果要拿具名插槽作用域 v-slot:插槽名称 = “scope”
3、新版本的语法里面只能通过template来在插槽中插入html,不能通过其他标签
最新的语法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<one>
<template #footer="{age,sex,userName}">
<p>{{userName}}</p>
<p>{{age}}</p>
<p>{{sex}}</p>
</template>
</one>
</div>
<template id="temp1">
<div class="box">
<slot></slot>
<h2>我是一个组件</h2>
<slot name="footer" :age="18" :sex="sex" :user-name="userName"></slot>
</div>
</template>
</body>
<script src="js/vue.js"></script>
<script>
var one = {
template:"#temp1",
data(){
return {
userName:"zhangsan",
sex:"女"
}
}
};
new Vue({
el:"#app",
components:{
one
}
})
</script>
</html>
在新版语法中,作用域是可以直接解构获取的,并且v-slot这个指令直接使用#替代,简化代码量,一般我们推荐第一种或第三种写法,第二种做一个了解
注意:
这里会有一个情况,就是作用域在向外传值的时候,动态绑定的属性值有可能会被认为是data的属性