1、为什么使用插槽
组件的插槽是为了让我们封装的组件更加具有扩展性。让使用者可以决定组件内部的一些内容到底展示什么。
例子:下图移动端的导航栏
导航栏我们必然会封装成一个插件,比如nav-bar组件。一旦有了这个组件,我们就可以在多个页面中复用了。
如何封装组件合适:
抽取共性,保留不同。
最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。一旦我们预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容。是搜索框,还是文字,还是菜单。由调用者自己来决定。
2、插槽的概念
插槽的关键字slot,默认情况下,组件中的模板会覆盖组件中的原始内容(即自定义标签对内部的内容会不显示),解决办法就是使用插槽。
插槽共分为3种:匿名插槽、具名插槽、作用域插槽
2.1、匿名插槽
匿名插槽的作用: 保留组件中的所有原始标签内容,这种插槽被称为匿名插槽,它保留了原始数据,除了具名插槽标签中的内容,即凡是标签中具有slot=top的属性标签。我们可以直接在组件中写上slot标签对,就可以在根元素中的引用的组件中间显示所写的内容。
匿名插槽:<slot></slot>
举例1:第一次使用插槽
需求,我们做一个如下的组件,但是我们想在各个组件中添加不同的内容,比如在第一个和第四个组件中加上button,第三个组件中加上p标签,第三个组件中加上i标签。这个时候就需要用到我们的插槽。
<div id="app">
<cpn>
<button type="button">按钮</button>
</cpn>
<cpn>
<p>这是段落标签</p>
</cpn>
<cpn>
<i>这是i标签</i>
</cpn>
<cpn>
<button type="button">按钮</button>
</cpn>
</div>
<template id="mycpn">
<div style="border: 1px solid red;margin-top: 20px;line-height: 10px;">
<h4>这是子组件的内容</h4>
<!-- 使用插槽 -->
<slot></slot>
</div>
</template>
<script src="./Vuejs/vue2-6-12.js"></script>
<script>
let cpn = {
template:"#mycpn",
}
new Vue({
el:"#app",
data:{
},
components:{
"cpn":cpn,
}
})
</script>
这里的slot可以理解成占位符,或者说是子组件暴露的一个让父组件传递自定义内容的接口。我们也可以在slot中添加默认内容,如做出下面修改:
<slot><button type="button">按钮</button></slot>
在我们的#app中创建如下cpn:
<div id="app">
<cpn>
<button type="button">按钮</button>
</cpn>
<cpn>
<p>这是段落标签</p>
</cpn>
<cpn>
<i>这是i标签</i>
</cpn>
<cpn>
<button type="button">按钮</button>
</cpn>
<cpn></cpn>
<cpn></cpn>
</div>
由此我们知道在slot中加上默认样式,倘若组件中添加了其他样式,如上面的p标签和i标签,这个时候我们的默认样式就会被覆盖,只有不添加任何内容,才会显示我们的默认样式。
2.2、具名插槽
当子组件的功能复杂时,子组件的插槽可能并非是一个。比如我们封装一个首页的子组件,可能就需要三个插槽,分别代表头部、中间、底部。那么,外面在给插槽插入内容时,如何区分插入的是哪一个呢?这个时候,我们就需要给插槽起一个名字。
凡是具有name属性的slot标签,就被称为具名插槽即<slot name=top>
(在子组件中写,写的位置不同,在引用该模板的页面中显示的位置也会不一样)。
作用:
①、 在组件的原始内容的某个标签中,添加slot=top属性,指明该标签所对应的 插槽的名称
②、在组件模板中通过调用slot标签,设置name=top属性,会自动将对应的标 签内容添加至当前slot标签所在的位置
注意:原始内容凡是具有slot属性的标签,内容只能添加至组件模板中具有相同值 的name属性的slot标签中。
具名插槽:<slot name=top></slot>
举例2:具名插槽的引入:
<div id="app">
<cpn>
<span>张三</span>
</cpn>
</div>
<template id="mycpn">
<div style="border: 1px solid red;margin-top: 20px;line-height: 10px;">
<h4>这是子组件的内容</h4>
<!-- 使用插槽 -->
<p>姓名:<slot>姓名</slot></p>
<p>性别:<slot>性别</slot></p>
<p>年龄:<slot>年龄</slot></p>
</div>
</template>
<script src="./Vuejs/vue2-6-12.js"></script>
<script>
let cpn = {
template:"#mycpn",
}
new Vue({
el:"#app",
data:{
},
components:{
"cpn":cpn,
}
})
</script>
当我们创建了多个插槽,但只想使用其中一个的时候,我们发现使用匿名插槽的方法会让所有的创建的插槽都被使用,如上例中,我们只想姓名打印出张三,而其他位置不变,但是最终结果是三个位置都打印出张三。这个时候我们就要用到具名插槽。
举例3:使用具名插槽
<!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>插槽学习</title>
</head>
<body>
<div id="app">
<cpn>
<span slot="uname">张三</span>
<span slot="uage">男</span>
<span slot="usex">18</span>
</cpn>
</div>
<template id="mycpn">
<div style="border: 1px solid red;margin-top: 20px;line-height: 10px;">
<h4>这是子组件的内容</h4>
<!-- 使用插槽 -->
<p>姓名:<slot name="uname">姓名</slot></p>
<p>性别:<slot name="uage">性别</slot></p>
<p>年龄:<slot name="usex">年龄</slot></p>
</div>
</template>
<script src="./Vuejs/vue2-6-12.js"></script>
<script>
let cpn = {
template:"#mycpn",
}
new Vue({
el:"#app",
data:{
},
components:{
"cpn":cpn,
}
})
</script>
</body>
</html>
我们也可以使用v-bind动态绑定我们的name属性。
2.3、作用域插槽
官方对于编译的作用域解析比较简单,我们自己来通过一个例子来理解这个概念:
官方给出了一条准则:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
<div id="app">
<cpn></cpn>
</div>
<template id="mycpn">
<div>
<h2>我能不能显示出来呢?</h2>
</div>
</template>
<script src="./Vuejs/vue2-6-12.js"></script>
<script>
let cpn = {
template:"#mycpn",
data(){
return{
isshow:false
}
}
}
new Vue({
el:"#app",
data:{
isshow:true,
},
components:{
"cpn":cpn,
}
})
</script>
我们在使用cpn的时候,整个组件的使用过程是相当于在父组件中出现的。
那么他的作用域就是父组件,使用的属性也是属于父组件的属性。
因此,isShow使用的是Vue实例中的属性,而不是子组件的属性。
因此我们的作用域插槽就解决了这个问题,父组件替换插槽的标签,但是内容是由子组件来提供。当组件需要在多个父组件多个界面展示的时候,将内容放在子组件插槽中,父组件只需要告诉子组件使用什么方式展示界面。
子组件中包括一组数据,比如:pLanguages: [‘JavaScript’, ‘Python’, ‘Swift’, ‘Go’, ‘C++’]
内容在子组件,希望父组件告诉我们如何展示,怎么办呢?
利用slot作用域插槽就可以了。
举例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>作用域插槽的基本使用</title>
</head>
<body>
<!-- 父组件 -->
<div id="app">
<!-- 使用组件 -->
<cpn></cpn>
<!-- 以数组的形式展示 -->
<cpn>
<!-- 使用v-slot接收具名插槽abc的数据,并把他命名成aa(名称是自定义) -->
<template v-slot="aa">
{{aa.abc}}
</template>
</cpn>
<!-- 以水平方向 ‘-’的方式来显示 -->
<cpn>
<!-- 使用v-slot接收具名插槽abc的数据,并把他命名成aa(名称是自定义) -->
<template v-slot="bb">
<!-- {{bb.abc}}- -->
{{bb.abc.join(' - ')}}
</template>
</cpn>
<cpn>
<!-- 使用v-slot接收具名插槽abc的数据,并把他命名成aa(名称是自定义) -->
<template v-slot="bb">
<!-- {{bb.abc}}- -->
{{bb.abc.join(' * ')}}
</template>
</cpn>
</div>
<!-- 子组件的模板 -->
<template id="mycpn">
<div style="border: 2px solid #FF0000; margin: 20px 50px;">
<h4>这是子组件的内容</h4>
<!-- 作用域插槽:v-slot -->
<!-- 定义具名插槽abc 并且传入数据pLanguages -->
<slot :abc="pLanguages">
<ul v-for="item in pLanguages">
<li>{{item}}</li>
</ul>
</slot>
</template>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.js"></script>
<script type="text/javascript">
// 创建子组件选项对象
let cpn ={
template:'#mycpn',
data(){
return{
ishow:false,
pLanguages: ['JavaScript', 'Python', 'Swift', 'Go', 'C++']
}
}
}
// 创建实例
var app=new Vue({
el:'#app',
data:{
ishow:true,//vue实例中的属性
},
components:{
// 注册cpn组件
cpn
}
})
</script>
</body>
</html>