vue插槽解决的问题:引入的子组件标签中间不允许写内容的。插槽(Slot)是vue为组件的封装者提供的能力;允许开发者在封装组件时,把不确定的、希望由用户指定的部分定义为插槽;可以把插槽认为是组件封装期间,为用户预留的内容的占位符。
本教程操作环境:windows7系统、vue3版,DELL G3电脑。
什么是插槽?
我们知道,在vue中,引入的子组件标签中间是不允许写内容的。为了解决这个问题,官方引入了插槽(slot)的概念。
插槽,其实就相当于占位符。它在组件中给你的HTML模板占了一个位置,让你来传入一些东西。插槽又分为匿名插槽、具名插槽以及作用域插槽。
你可能不太明白,为什么我要给子组件中传入HTML,而不直接写在子组件中呢?答案是这样的。你可以想象一个场景,你有五个页面,这五个页面中只有一个区域的内容不一样,你会怎么去写这五个页面呢?复制粘贴是一种办法,但在vue中,插槽(slot)是更好的做法。
匿名插槽
匿名插槽,我们又可以叫它单个插槽或者默认插槽。与具名插槽相对,它不需要设置name属性。(它隐藏的name属性为default。)
例子:
文件目录如下,Home组件是HelloWorld的父组件。
- 在HelloWorld中写一个匿名插槽
<template> <div class="hello"> Helloworld组件 <div class = 'slotTxt'> <slot></slot> </div> </div> </template> <script> export default { } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped lang="less"> .hello{ width:100%; height:300px; background:#ccc; margin-top:50px; .slotTxt{ width:500px; height:200px; margin:30px auto; background:red; } } </style>
- 在Home组件中引入子组件,并在子组件标签中写入内容
<template> <div class="home"> 我是Home父组件 <HelloWorld> <!-- 没有插槽,这里的内容不显示 --> <h1>我是helloworld中的插槽啊</h1> </HelloWorld> </div> </template> <script> import HelloWorld from '@/components/HelloWorld.vue' export default { name: 'home', components: { HelloWorld } } </script>
效果
不难看出,HelloWorld标签中的内容(红色部分)已经显示出来了。
具名插槽
上面已经说过,插槽有一个name属性。与匿名插槽相对,加了name属性的匿名插槽就是具名插槽。
- HelloWorld组件中写入name属性分别为left和right的插槽
<template> <div class="hello"> Helloworld组件 <div class = 'slotLeft'> <slot name='left'></slot> </div> <div class = 'slotRight'> <slot name='right'></slot> </div> </div> </template> <script> export default { } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped lang="less"> .hello{ width:700px; height:300px; background:#ccc; margin: 0 auto; margin-top:50px; .slotLeft{ width:300px; height:200px; float:left; background:red; } .slotRight{ width:300px; height:200px; float:right; background:pink; } } </style>
- Home组件通过在template上写v-slot:name来使用具名插槽
<template> <div class="home"> 我是Home父组件 <HelloWorld> <template v-slot:left> <h1>name属性为left</h1> </template> <template v-slot:right> <h1>name属性为right</h1> </template> </HelloWorld> </div> </template> <script> import HelloWorld from '@/components/HelloWorld.vue' export default { name: 'home', components: { HelloWorld } } </script> <style lang="less" scoped> .home{ width:900px; margin:0 auto; background:yellow; padding-bottom:100px; } </style>
注意 v-slot 只能添加在template标签上 (只有一种例外情况)。
-
效果
- 例外情况(被废弃的slot=‘name’)
带slot属性的具名插槽自 2.6.0 起被废弃,vue3.x被完全废弃。只有vue3之前的cli可以使用。
<template> <div class="home"> 我是Home父组件 <HelloWorld> <h1 slot='left'>name属性为left</h1> <h1 slot='right'>name属性为right</h1> </HelloWorld> </div> </template> <script> import HelloWorld from '@/components/HelloWorld.vue' export default { name: 'home', components: { HelloWorld } } </script> <style lang="less" scoped> .home{ width:900px; margin:0 auto; background:yellow; padding-bottom:100px; } </style>
效果同上。
- 具名插槽的小知识点
跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header。
作用域插槽
作用域插槽其实就是可以传递数据的插槽。子组件中的一些数据想在父组件中使用,必须通过规定的方法来传递。在官方文档中提出了一条规则,**父级模板里的所有内容都是在父级作用域中编译的。子模板里的所有内容都是在子作用域中编译的。**如果你在父组件直接使用子组件中的值,是会报错的。
匿名插槽的作用域插槽
为了让 子组件中的数据 在父级的插槽内容中可用,我们可以将 数据 作为 元素的一个特性绑定上去:
语法:v-bind:users="user"
- 子组件HelloWorld代码
<template> <div class="hello"> Helloworld组件 <div class='slotLeft'> <slot v-bind:users="user"></slot> </div> </div> </template> <script> export default { data(){ return{ user:{ name:'oralinge', age:18 } } } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped lang="less"> .hello{ width:700px; height:300px; background:#ccc; margin: 0 auto; margin-top:50px; .slotLeft{ width:300px; height:200px; // float:left; background:red; margin:20px auto } .slotRight{ width:300px; height:200px; float:right; background:pink; } } </style>
绑定在 元素上的特性(v-bind:users=“user”)被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字。
语法:v-slot:default="随意取的名字" // default可省略,简写为v-slot="随意取的名字"
- 父组件Home代码
<template> <div class="home"> 我是Home父组件 <HelloWorld> <template v-slot:default="slotProps"> <h1>{{slotProps.users.name}}</h1> </template> </HelloWorld> </div> </template> <script> import HelloWorld from '@/components/HelloWorld.vue' export default { name: 'home', components: { HelloWorld } } </script> <style lang="less" scoped> .home{ width:900px; margin:0 auto; background:yellow; padding-bottom:100px; } </style>
注意:
父组件中的slotProps可以是随意取的。
子组件中users是随意取的,与之对应的是父组件中的users。
子组件中的user为数据。
效果
具名插槽的作用域插槽
与匿名插槽同理,只需要把default替换成插槽的name值即可。
- 子组件HelloWorld代码
<template> <div class="hello"> Helloworld组件 <div class='slotLeft'> <slot name='helloWorld' v-bind:users="user"></slot> </div> </div> </template> <script> export default { data(){ return{ user:{ name:'hello world', age:18 } } } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped lang="less"> .hello{ width:700px; height:300px; background:#ccc; margin: 0 auto; margin-top:50px; .slotLeft{ width:300px; height:200px; // float:left; background:red; margin:20px auto } .slotRight{ width:300px; height:200px; float:right; background:pink; } } </style>
- 父组件Home代码
<template> <div class="home"> 我是Home父组件 <HelloWorld> <template v-slot:helloWorld="slotProps"> <h1>{{slotProps.users.name}}</h1> </template> </HelloWorld> </div> </template> <script> import HelloWorld from '@/components/HelloWorld.vue' export default { name: 'home', components: { HelloWorld } } </script> <style lang="less" scoped> .home{ width:900px; margin:0 auto; background:yellow; padding-bottom:100px; } </style>
效果
注意:
默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确。
另,slot-scope写法在2.6之后已废弃,作用与上面相同,在此不做解释。
上面的写法是不是觉得有些麻烦?别着急,我们来看一看解构插槽 Prop。
解构插槽 Prop
作用域插槽的内部工作原理是将你的插槽内容包括在一个传入单个参数的函数里:
function (slotProps) { // 插槽内容 }
这意味着 v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式。所以在支持的环境下 (单文件组件或现代浏览器),你也可以使用 ES2015 解构来传入具体的插槽 prop。
语法:v-slot="{ users }"
- HelloWold组件
<template> <div class="hello"> Helloworld组件 <div class='slotLeft'> <slot v-bind:users="user"></slot> </div> </div> </template> <script> export default { data(){ return{ user:{ name:'hello world', age:18 } } } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped lang="less"> .hello{ width:700px; height:300px; background:#ccc; margin: 0 auto; margin-top:50px; .slotLeft{ width:300px; height:200px; // float:left; background:red; margin:20px auto } .slotRight{ width:300px; height:200px; float:right; background:pink; } } </style>
- Home组件
<template> <div class="home"> 我是Home父组件 <HelloWorld> <template v-slot="{ users }"> <h1>{{users.name}}</h1> </template> </HelloWorld> </div> </template> <script> import HelloWorld from '@/components/HelloWorld.vue' export default { name: 'home', components: { HelloWorld } } </script> <style lang="less" scoped> .home{ width:900px; margin:0 auto; background:yellow; padding-bottom:100px; } </style>
-
效果
- 重命名—-更改users这个名字
<template> <div class="home"> 我是Home父组件 <HelloWorld> <template v-slot="{ users:person }"> <h1>{{person.name}}</h1> </template> </HelloWorld> </div> </template> <script> import HelloWorld from '@/components/HelloWorld.vue' export default { name: 'home', components: { HelloWorld } } </script> <style lang="less" scoped> .home{ width:900px; margin:0 auto; background:yellow; padding-bottom:100px; } </style>
效果如上图。
- 定义后备内容,用于插槽 prop 是 undefined 的情形
此处按照官方文档的写法会出现语法报错,后期应该会修复(有知道的麻烦通知一声)。
<template> <div class="home"> 我是Home父组件 <HelloWorld> <template > <h1 v-slot="{ users = { name: '1111' } }">{{users.name}}</h1> </template> </HelloWorld> </div> </template> <script> import HelloWorld from '@/components/HelloWorld.vue' export default { name: 'home', components: { HelloWorld } } </script> <style lang="less" scoped> .home{ width:900px; margin:0 auto; background:yellow; padding-bottom:100px; } </style>
使用场景
- 复用公共组件
代码示例如下:
<template> <div> <div class="title-box"> <span class="title">{{title}}</span> <div class="right"> <slot name="right"></slot> </div> </div> <div class="content-box"> <slot></slot> </div> </div> </template> <script> export default { data () { return { } }, props: { title: { type: String, required: true } } } </script> <style lang="scss" scoped> .title-box { padding: 16px 0; border-bottom: 1px solid #eff1f5; .title { font-family: MicrosoftYaHei; font-size: 24px; color: #283039; letter-spacing: 0; line-height: 24px; &::before { width: 4px; margin-right: 20px; content: ""; background-color: #5da1ff; display: inline-block; height: 20px; vertical-align: middle; } } .right { float: right; margin-right: 20px; } } </style>
使用的ui框架为ivew。