# 1、使用思考
经过《自定义事件》和《【全局事件总线】》这两个章节的洗礼,现在是不是满脑子都是:父子组件、数据传递之类的东西 😂。
但是,但是,总是传递一点点的数据怎么可以满足,格局小了。要传就传大的:template 模板怎么样?
在前面,我们对子组件的使用,仅仅只是在属性层面,换一个角度,在内容层面会发生什么情况呢?
- 父组件
<template>
<hello-world>随便写一下内容吧</hello-world>
</template>
- 子组件
<template>
<div class="hello">
<!-- 插槽:接收内容 -->
<slot></slot>
</div>
</template>
# 2、默认插槽
但有时候,我们在编写子组件的时候,父组件使用插槽时可能并不会如我们所愿:他不会向插槽提供内容。
此时我们就是设置一个默认内容以备不时之需 —— 默认插槽。
<template>
<div class="hello">
<!-- 为插槽设置默认内容 -->
<slot>这是子组件默认内容</slot>
</div>
</template>
# 3、具名/动态插槽
当然,像一些组件库中若使用插槽的话,大概率会提供多个插槽供我们使用,这个时候我们就需要:
父组件中使用
v-slot
(template 标签 ✍ 上,可以简写为#
)
子组件中使用name 属性
(slot 标签上)
应用:vant 导航栏
以其导航组件中的 NavBar 导航栏为例:通过#right 可以自定义 vant 组件库封装好的导航右边的内容。
<van-nav-bar title="标题" left-text="返回" left-arrow>
<template #right>
<van-icon name="search" size="18" />
</template>
</van-nav-bar>
这样就可以将内容和插槽区域一一对应了(如下面的 title):
- 父组件
<!-- 1、具名插槽 -->
<template>
<div class="home">
<hello-world>
<!-- -->
<template #title>
<h3>量身定制</h3>
</template>
</hello-world>
</div>
</template>
<!-- =========================== -->
<!-- 2、动态插槽 -->
<template>
<div class="home">
<hello-world>
<!-- -->
<template v-slot:[name]>
<h3>量身定制</h3>
</template>
</hello-world>
</div>
</template>
- 子组件
<template>
<div class="hello">
<!-- -->
<slot name="title"></slot>
</div>
</template>
# 4、作用域插槽 🎈
前面我们传的都是纯 template 模板,那在父组件中:如果子组件标签内 template 模板涉及到要使用子组件中的数据怎么办?
提示
作用域插槽,在一些开源项目、组件封装中应用比较广泛,比如 element-plus 中的插槽(其提供的 scope):
应用:element 表格
- 自定义表头/列模板
<template>
<el-table :data="tableList">
<el-table-column>
<template #header>
<!-- …… -->
</template>
<template #default="scope">
<!-- scope.$index, scope.row -->
</template>
</el-table-column>
</el-table>
</template>
当然,你可以单独的使用自定义事件来获取子组件数据,但这违背了子组件使用插槽的初衷,具体解决方案为:
在父组件中,我们使用
slot
属性去匹配子组件插槽的 name 属性,然后我们可以通过slot-scope
属性获取子组件插槽上通过 props 传递的数据(不同于常规的父传子的 props 选项 接收数据)。
上述方案也就是 vue 中的作用域插槽,其中还有一些细节:
- slot 标签上使用的 props(绑定在
<slot>
元素上的 attribute 被称为插槽 prop) slot
属性 和slot-scope
属性在 vue2.6.0 开始合并为一个新指令v-slot
关于 v-slot 指令 🤔 的发展史
默认插槽中,它们一一对应的值为default
-v-slot
可以简写为 #
,例如:v-slot:default 和 #default
<template #default>
<h3>量身定制</h3>
</template>
<!-- …… -->
<slot name="default"></slot>
-v-slot
是 2.6.0 版本开始为具名插槽和作用域插槽引入新指令(语法糖)
<template>
<div>
<hello-world>
<!-- 1、新命令 -->
<template #title="slotProps">
<!-- 略 -->
</template>
<!-- 2、旧命令 -->
<template slot="title" slot-scope="slotProps">
<!-- 略 -->
</template>
</hello-world>
</div>
</template>
<!-- …… -->
<slot name="title" v-bind="propsObj"></slot>
简单使用
- 子组件
<template>
<div class="hello">
<!-- 定义一个插槽prop传送数据 -->
<slot name="title" :classifyList="slotData" :prop="classify">
<!-- 默认内容 -->
<div>
<h3>title</h3>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
</div>
</slot>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
slotData: [
{
title: '水果',
lis: ['苹果', '香蕉', '橙子']
},
{
title: '动物',
lis: ['熊猫', '老鹰', '猴子']
}
]
}
}
}
</script>
- 父组件
<template>
<div class="home">
<h3>HomeView组件内容</h3>
子组件内容如下:
<hello-world>
<!-- 数据、位置都🚩可以确定 -->
<template #title="slotProps">
<!-- {{ slotProps.classifyList, slotProps.props }} -->
<div v-for="(item, index) in slotProps.classifyList" :key="index">
<h3>{{ item.title }}</h3>
<ul v-for="(i, idx) in item.lis" :key="idx">
<li>{{ i }}</li>
</ul>
</div>
</template>
</hello-world>
</div>
</template>
进一步的,我们还可以通过解构赋值简化父组件获取的 slotProps 数据操作:
<template>
<div class="home">
<hello-world>
<template #title="{ scope }">
<!-- 略 -->
</template>
</hello-world>
</div>
</template>
拓展: 独占 👀 默认插槽
使用:
<template>
<div class="home">
<hello-world>
<!-- <template v-slot:default="slotProps"> -->
<!-- 简写 -->
<template v-slot="slotProps">
<!-- 略 -->
</template>
</hello-world>
</div>
</template>
应用:
如果只有一个默认插槽,那么 template 可以省略(直接写在子组件标签上)
<template>
<div class="home">
<hello-world v-slot="slotProps">
<!-- 略 -->
</hello-world>
</div>
</template>
# 5、拓展
如果你使用的 vue 版本低于 2.6.0,你就得使用旧命令了:
<hello-world>
<!-- 1、新命令 -->
<template #title="slotProps">
<!-- 略 -->
</template>
<!-- 2、旧命令 -->
<template slot="title" slot-scope="slotProps">
<!-- 略 -->
</template>
</hello-world>