插槽的作用
在生活中,拿我们的电脑举例,它有很多接口,比如说USB接口,耳机接口等等。这些接口插槽的作用就是为了让电脑更具拓展性。比如我们能通过USB链接鼠标,操作我们的电脑,能连接u盘,存储数据....我们的slot同样是这个作用。他也是为了让我们的组件更具拓展性,用户能自定义组件的某些内容。使得组件适用于各种各样的环境。
example:移动端的导航栏
移动端的导航栏明显不是一摸一样的,在不同的页面都会存在细微的差别。但是他们大体结构上完全一致。如果我们将每个页面的导航栏封装成一个独立的组件,又显得很浪费。感觉没有必要。那我们该怎么做呢?
- 第一步,求同,把他们相同的部分封装在组件中。
- 第二步,存异,把他们在各个页面显示不同内容的部分设置成插槽的形式,使得在不同页面可以自定义显示的内容。
这些大概就是我们插槽的作用所在。
插槽的基本使用
从需求出发,比如说这里我有三个相同的组件,每个组件显示的内容略有不同。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./js/vue.js"></script>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<script>
const app = new Vue({
el:"#app",
components:{
cpn:{
template:`<div>
<h1>我是子组件</h1>
<slot></slot>//子组件预留插槽
</div>`,
}
}
})
</script>
</body>
</html>
第一个组件我想显示一个button,第二个组件我想显示一段文本,第三个组件我想显示一张图片。那么问题来了我们在哪添加自定义内容呢?
答案是使用子组件时,自定义内容添加在子组件标签内部。比如这里我想实现需求应该是这样做:
<div id="app">
<cpn>
<button>点击</button>
</cpn>
<cpn>
<p>
我是猪
</p>
</cpn>
<cpn>
<img src="https://cdn.chime.me/image/fs/sitebuild/202091/0/original_1904bfb4-855c-4e3a-9c11-d3c088a76eee.jpeg" alt="">
</cpn>
</div>
效果图:
如果你没有添加自定义内容,那么插槽就不显示。
插槽默认值
当你某个插槽绝大多数情况都是显示某个内容,只有极个别情况显示其他内容。那这个时候就可以给插槽设置默认值了。就是你在设置插槽的时候在slot标签内部写的内容就是该插槽的默认值。
components:{
cpn:{
template:`<div>
<h1>我是子组件</h1>
<slot>
<h1>woshizhu</h1>//这个地方就是该插槽的默认值。
</slot>//子组件预留插槽
</div>`,
}
}
当你没有设置自定义内容的时候,插槽就显示自己的默认值。
具名插槽的使用
一个组件可能不只有一个插槽,比如说我这里有三个插槽:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./js/vue.js"></script>
</head>
<body>
<div id="app">
<cpn>
</cpn>
</div>
<script>
const app = new Vue({
el:"#app",
components:{
cpn:{
template:`<div>
<h1>我是子组件</h1>
<slot><h1>左边</h1></slot>
<slot><h1>中间</h1></slot>
<slot><h1>右边</h1></slot>
</div>`,
}
}
})
</script>
</body>
</html>
显示效果:
我如果想给中间的插槽自定义内容该怎么办呢?
如果我还是原来设置自定义内容的方法显然是不行的。
<div id="app">
<cpn>
<h1>我要去中间</h1>//定义的是所有不具名插槽的显示内容
</cpn>
</div>
显示效果:
显然,我这种设置自定义内容的方式,是指定所有不具名插槽的显示内容。
那如果我要给某个特定的插槽设置特定的内容该怎么做呢?
- 通过name属性给插槽起名字(具名插槽)
<slot name="mid"></slot> - 通过slot属性给自定义内容指定插槽
<div id="app">
<cpn>
<h1 slot="mid">我要去中间</h1>
</cpn>
</div>
效果图:
注意
不具名的插槽其实都有一个隐式的名字:default
具名插槽的更新
在vue 2.6之后,slot属性被废弃了,而是用一种新的形式代替它的功能:使用v-slot指令。这个指令只能使用在template元素上。(这点不同于slot属性可以在任意元素上使用,也就导致具名插槽的内容的根元素只能是template)。那上面的代码就应该这样来实现。
<div id="app">
<cpn>
<template v-slot:mid>
<h1>我要去中间</h1>
</template>
</cpn>
</div>
任何被template包裹的内容都会传递给相对应的具名插槽,而那些没有被template包裹的内容会被当做不具名插槽的内容。
如果想明确给不具名插槽指定内容可以这样写:
<cpn>
<template v-slot:default>
不具名插槽的内容
</template>
</cpn>
因为不具名插槽的名字就是default。
编译作用域
我们还是从一段代码出发
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./js/vue.js"></script>
</head>
<body>
<div id="app">
<cpn>
{{message}}
</cpn>
</div>
<script>
const app = new Vue({
el:"#app",
data:{
message:"父组件"
},
components:{
cpn:{
template:`<h1>
<slot></slot>
</h1>`,
data(){
return {
message:"子组件"
}
}
}
}
})
</script>
</body>
</html>
简单描述一下,这里就两个组件:父子组件。并且父子组件的data中都含有一个变量:message.那么如果我们设置子组件的内容的时候,也就是定义插槽内容的时候:<cpn> {{message}}</cpn>,在子组件的标签内我们使用的message到底是父组件的message,还是子组件的message呢?
答案是父组件的message.
这里会有两个误区:
- 逻辑思维上的误区:我在子组件标签内写代码,那当然是优先在子组件中找对应数据(错)
- 主观臆断上的误区:由于我设置的内容本质上是子组件template的一部分,所以我使用的数据当然是子组件内部。前半句对,后半句错。
这里涉及到编译的作用域的问题
我个人认为这个是一个特别简单的问题,我的理解就是数据在在哪个组件的模板出现,那么一定是从这个组件的数据中找。现在是根实例的模板,所以只会从根实例的数据中找。如果是在子组件的模板中用到该数据当然是在子组件的数据中找。
作用域插槽
很多情况下,我们还是希望定义插槽的内容的时候能使用子组件中的数据。(毕竟插槽的内容就是子组件的一部分)
**那我们怎么设置插槽内容的时候使用子组件的数据呢? **
- 在slot标签内我们应该把想要在插槽中使用的数据传递出来。
怎么传递出来呢?通过自定义属性传递出来。那就说明你这个插槽就有了这个自定义属性。 - 在设置slot内容的时候,在父元素中调用slot-scope属性就可以取得这个取得自定义属性组成的对象(属性名作为对象的属性名,属性值作为对象的属性值)
并且通过我实验的代码可知,取得的自定义属性组成的对象是和slot名对应的。也就是说所有不具名插槽定义的自定义属性(也就是名为default的slot)只能在设置不具名插槽的内容时,你可以通过slot-scope访问到,设置具名插槽的内容时,通过slot-scope时访问不到的。。。。算了好乱还是举例子说明吧。。。
example:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./js/vue.js"></script>
</head>
<body>
<div id="app">
<cpn>
<div slot-scope="props">
名为default插槽的内容
{{props.lefta}}
{{props.rightc}}
</div>
<div slot="right" slot-scope="rightprops">
名为right插槽的内容
{{rightprops.rightc}}
{{rightprops.lefta}}
</div>
</cpn>
</div>
<script>
const app = new Vue({
el:"#app",
data:{
a:''
},
components:{
cpn:{
template:`<div>
<slot lefta="lefta"></slot>//default插槽自定义了lefta属性
<slot name="right" rightc="rightc" ></slot>//right插槽自定义了rightc属性
</div>`
}
}
})
</script>
</body>
</html>
就是说,default插槽自定义了lefta属性,而right插槽自定义了rightc属性,而当你在设置default插槽的内容的时候,slot-scope对应的对象里面的属性只包含default插槽自定义的属性,而不会包含right插槽自定义的属性。所以
props.lefta存在。 props.rightc不存在。同理,设置right插槽的内容的时候,slot-scope对应的对象肯定也不含lefta属性。
作用域插槽的更新
实际上,在Vue2.6以上之后,slot和slot-scope属性就被废除了,那么怎么实现在插槽中访问子组件的数据呢?其实是类似的只是不用slot-scope属性了。
//原来的写法
<cpn>
<div slot-scope="props">
名为default插槽的内容
{{props.lefta}}
{{props.rightc}}
</div>
<div slot="right" slot-scope="rightprops">
名为right插槽的内容
{{rightprops.rightc}}
{{rightprops.lefta}}
</div>
</cpn>
//现在的写法
<cpn>
<template v-slot:default="props">
<div>
名为default插槽的内容
{{props.lefta}}
{{props.rightc}}
</div>
</template>
<template v-slot:right="rightprops">
<div>
名为right插槽的内容
{{rightprops.rightc}}
{{rightprops.lefta}}
</div>
</template>
</cpn>
其实还是挺简单的。
v-slot:指令的简写
很多指令都有语法糖,这个也不例外,这个的语法糖就是#
上文的改造
<cpn>
<template #default="props">
<div>
名为default插槽的内容
{{props.lefta}}
{{props.rightc}}
</div>
</template>
<template #right="rightprops">
<div>
名为right插槽的内容
{{rightprops.rightc}}
{{rightprops.lefta}}
</div>
</template>
</cpn>
解构赋值和作用域插槽
其实也挺简单的,例如上文的#default可以理解成是自定义属性组成的对象,你可以把他直接赋值给props,那么props就是这个对象。通过它访问里面的属性。也可以直接进行解构赋值({lefta:lefta}={lefta}),取得里面的属性值。(获得变量lefta值为字符串"lefta")
//进行解构赋值
<template #default="{lefta}">
<div>
{{lefta}}
</div>
</template>