插槽

@Slots
Introduction
定义的组件除了传递属性 Props 和 传递事件 Emits,还可以通过 插槽 Slots 传递内容 content
使用特殊元素 <slot> 在组件中预留位置,以便根据需要插入内容,从而拓展功能 - more flexible and reusable
可以是任何有效的内容;静态数据,响应式数据,甚至其它组件 - can be any valid template content
可以指定插槽默认内容
可以声明多个插槽;为了区分,多个插槽应该指定 name 属性
大规模应用于前端框架UI中,如 Vant
一个插槽
只预留一个位置
1. 子组件 Test.vue 创建过程中,预留一个位置/一个占位符,父组件传递进来的内容就会渲染在这里
<div>
  //其它内容
  <slot />
</div>
可以指定默认内容
<div>
  //其它内容
  <slot>插槽默认内容</slot>
</div>
2. 父组件 - 不可以使用单标签形式;使用内容填充插槽;可以是普通文本,也可以是HTML节点
<Test>
  Something bad happened. 
</Test>
使用默认内容的插槽
<Test> </Test>
多个插槽
预留多个位置/多个插槽
使用属性 name 区分 - 又称具名插槽 Named Slots
没有指定 name 的,使用默认名字 default
同样可以指定默认内容
前端UI框架用大量使用插槽使用达到结构复用,如 Vant
<div>
    //其它内容
    <slot></slot>
    <slot name = 'header'></slot>
    <slot name = 'footer'></slot>
</div>
使用具名插槽 - 使用 <template> 并指定对应的 v-slot 指令;可以简写为 #
<Test>  
  <template v-slot:header>
      <h1>Here might be a page title</h1>
  </template>
  <template #footer>
      <h1>Here might be a page footer</h1>
  </template>
</Test>
条件插槽*
条件插槽 Conditional Slots - 利用插槽的 $slots 属性判断
多用于具名插槽;只有当父组件提供了相应的插槽内容时,才会渲染对应的 DOM 结构;非条件插槽则必须使用
<div>
    //内容
    <slot></slot>
    <slot name = 'header'></slot>
    <div v-if="$slots.footer">
        <slot name = 'footer'></slot>
    </div>
</div>
使用条件插槽 - 不指定 footer
<Test>  
    <template v-slot:header>
        <h1>Here might be a page title</h1>
    </template>
</Test>
动态插槽
使用 动态参数(需要包含在一对方括号内)
插槽样式 Style
插槽仅仅是指定|预留了一个占位符;子组件:
. 无法预料/不确定父组件使用这个插槽传递什么
. 无法为插槽指定类或内联样式
. 即使预判到使用什么,也无法指定样式
更多样式细节,请访问 单文件组件 CSS 功能
[] 版权组件 - 默认插槽,这里以传递一个 <img> 为例
子组件 - 设置图片的样式无效;;按F12在开发者视图中,检查元素页可以确定,没有样式
img {
    width: 30px;
    height: 30px;
    border-radius: 50%;
}
子组件 - 为插槽指定样式 avatar,同样无效
<template>
    <div class="copyright">
        <span>copyright @ 2024-2026</span>
        <slot class="avatar"></slot>
        <span>glpla.github.io</span>
    </div>
</template>
解决方案1 - 父组件指定样式,因为这个插槽是父组件提供的,对父组件是可见的
可以直接指定样式;建议使用插槽组件的同名类名作为范围限制
.copyright img {
    width: 30px;
    height: 30px;
    border-radius: 50%;
}
解决方案2 - 子组件使用 伪类选择器 :slotted(selector),明确地将插槽内容作为选择器的目标
默认情况下,子组件作用域内的样式不会影响到 <slot> 渲染出来的内容 - 是父组件所持有并传递进来的
子组件使用插槽标签选择器 - Bad
:slotted(img) {
    width: 30px;
    height: 30px;
}
子组件使用插槽类选择器 - Good
:slotted(.avatar-img) {
    width: 30px;
    height: 30px;
}
但是。但是。。但是。。。子组件并不知道父组件到底使用插槽传递什么,你要预判所有的可能,写尽可能多的类样式
IDE可能会提示 ::slotted 而不是 :slotted。注意修正
插槽作用域 scoped slots
插槽数据的访问
插槽内容可以使用/访问父组件域内的数据 - Slot content has access to the data scope of the parent component
如果还想使用子组件域内的数据 - 子组件在渲染时将一部分数据提供给插槽
传递 props 那样,向一个插槽的出口传递 attributes
接收插槽 props 时,默认插槽和具名插槽的使用方式有一些小区别;建议使用具名插槽
默认插槽 - default slots
创建插槽组件 - 为默认插槽指定属性 msg 并绑定一个数据 msg
<div>
  <div>Lorem ipsum dolor sit amet.</div>
  <slot :msg="msg"></slot>
</div>
使用插槽组件 - 使用 v-slot 指定 一个插槽 props 对象 并访问;属性对象名可以是任何合法标识符,建议使用 slot 作为前缀或者后缀,如 propsSlot;如果属性不匹配,并不会告警
<Test v-slot="propsSlot">;
  {{ propsSlot.msg }}
</Test>;
使用解构更简洁 - 省略了为属性命名的麻烦
<Test v-slot="{ msg }">;
  {{ msg }}
</Test>;
假设指定了多个属性
<Test v-slot="{{ msg, age }}">;
  {{ msg }} {{ age }}
</Test>;
具名插槽 - named slots
插槽组件 - 为具名插槽指定 name 和属性 msg 并绑定一个数据 msg
<div>
  <div>Lorem ipsum dolor sit amet.</div>
  <slot name="greeting" :msg="msg"></slot>
</div>
指定具名插槽的同时,指定属性对象
<Test>
  <template #greeting="propsSlot">{{ propsSlot.msg }}</template>
</Test>
同样可以使用解构
<Test>
  <template #greeting="{ msg }">{{ msg }}</template>
</Test>
Drill
[] 标题组件 Title.vue - 插槽版
. 需要什么标题就插入什么标题;标签和内容任意
. 创建组件
<div class="title">
  <slot></slot>
  <button @click="handle">查看全部 ></button>
</div>
. 使用组件
<Title :handle="toWelfare">
  <h3>福利中心</h3>
</Title>
Summary & Homework
Summary
<slot>:默认插槽、具名插槽
v-slot / #
插槽样式 - 建议由父组件指定
插槽作用域
Homework
[] 版权组件进阶版 - 插槽版
. 如果不指定内容则显示学院名字或小组名字
<div class="copyright">
  <img class="img" :src='src' alt="">
  <slot>广州新华学院</slot>
  <div class="desc">2024 - 2026 © Copyright, powered by {{ id }}</div>
</div>
[] 封装卡片组件,用户可以使用顶部标题和底部说明
[] 封装代码组件,以便区分是结构、逻辑还是样式
[] 插槽 slots 和属性宏 defineProps() 有什么异同?