Vue之插槽(slot)

插槽是vue中的一个非常强大且灵活的功能,在写组件时,可以为组件的使用者预留一些可以自定义内容的占位符。通过插槽,可以极大提高组件的客服用和灵活性。

插槽大体可以分为三类:默认插槽,具名插槽和作用域插槽。

下面将一一介绍。

①默认插槽

这种插槽没有指定名称,用于接受父组件传递的未明确指定插槽名称的内容。在子组件中使用<slot></slot>定义插槽所在位置,父组件在书写子组件的标签体里书写插入到该插槽的内容。

代码如下:

父组件:index.vue

html 复制代码
<!--
 * @Author: RealRoad
 * @Date: 2024-10-18 10:49:28
 * @LastEditors: Do not edit
 * @LastEditTime: 2024-11-14 14:13:02
 * @Description: 
 * @FilePath: \project_10_08\vite-project\src\views\home\index.vue
-->

<template>
 <div class="box">
  <Category class="content">
    <div>我是文本</div>
    <img src="https://img0.baidu.com/it/u=454995986,3330485591&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=375" alt="">
  </Category>
  <Category class="content">
    <el-button type="primary" size="default" @click="">一个按钮</el-button>
    
  </Category>
  <Category class="content">
    <el-card shadow="always" :body-style="{ padding: '20px' }">
      <div slot="header">
        <span>卡片标题</span>
      </div>
      <!-- card body -->
       <div>
        卡片体
       </div>
    </el-card>
    
  </Category>
 </div>
</template>

<script setup lang="ts">
import {ref,reactive} from 'vue'
import Category from './Category.vue'
</script>

<style scoped lang="scss">
.box{
  display:flex;
  justify-content: space-evenly;
  margin-top: 20px;
  .content{
    margin-left: 10px;
    background:pink;
    text-align: center;
    width: 400px;
    height: 600px;
    img{
      width: 100%;
    }
  }
}
</style>

子组件:Category.vue

html 复制代码
<template>
 <div>
  我是子组件
  <!-- 一个默认插槽 -->
  <slot>插槽的默认内容</slot>
 </div>
</template>

<script setup lang="ts">
import {ref,reactive} from 'vue'

</script>

<style scoped>

</style>

来看效果:

当然了,在子组件中,可以书写插槽的默认内容,就是说如果父组件没有书写任何内容,就会默认使用子组件插槽内的内容。

再写一个子组件,看一下效果

②具名插槽

顾名思义,就是带有名称的插槽,用于接受父组件中明确指定插槽名称的内容。

这里需要注意,vue2和vue3的写法略有不同,因为v3兼容v2,所有有些老版本的项目写的插槽还是v2的写法。

首先看v3的具名插槽写法:

子组件的写法相同,在子组件中使用<slot name="插槽名"></slot>就可以给插槽起一个名字。

子组件(NamedSlot.vue):

html 复制代码
<template>
 <div>
  我是子组件2
  <!-- 一个默认插槽 -->
  <slot name="top">插槽的默认内容</slot>
  <slot name="bottom">插槽的默认内容</slot>
 </div>
</template>

<script setup lang="ts">
import {ref,reactive} from 'vue'

</script>

<style scoped>

</style>

来到父组件(index.vue):v3

html 复制代码
<!--
 * @Author: RealRoad
 * @Date: 2024-10-18 10:49:28
 * @LastEditors: Do not edit
 * @LastEditTime: 2024-11-14 14:40:49
 * @FilePath: \project_10_08\vite-project\src\views\home\index.vue
 * @Description: 
-->

<template>
 <div class="box">
  <NamedSlot class="content">
    <template #top>
    <div >我是文本</div>
  </template>
  <template #bottom>
      <img src="https://img0.baidu.com/it/u=454995986,3330485591&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=375" alt="">

    </template>
  </NamedSlot>
  <Category class="content">
    <el-button type="primary" size="default" @click="">一个按钮</el-button>
    
  </Category>
  <Category class="content">
    <el-card shadow="always" :body-style="{ padding: '20px' }">
      <div slot="header">
        <span>卡片标题</span>
      </div>
      <!-- card body -->
       <div>
        卡片体
       </div>
    </el-card>
    
  </Category>
  <Category class="content"></Category>
 </div>
</template>

<script setup lang="ts">
import {ref,reactive} from 'vue'
import Category from './Category.vue'
import NamedSlot from './NamedSlot.vue';
</script>

<style scoped lang="scss">
.box{
  display:flex;
  justify-content: space-evenly;
  margin-top: 20px;
  .content{
    margin-left: 10px;
    background:pink;
    text-align: center;
    width: 400px;
    height: 600px;
    img{
      width: 100%;
    }
  }
}
</style>

可以对比一下上面的默认插槽,我们只修改了第一个子组件,其他的还是保持不变。

先看一下效果:

效果是一样的,不过是我们在子组件中起了名字,这样我们就可以在父组件随便改变顺序,就不用改变代码顺序了,直接修改插槽的名字即可。

说一下v3中的父组件的具名插槽的写法:

<子组件名称 >

<template #插槽名>

插槽内容

</template>

</子组件名称>

这样的格式,借用了template标签,并在标签上使用#的简写形式,也是现在element-plus等新版UI组件库使用的方式。

说完了v3,那一定要说一下老版本的v2写法,毕竟老项目中的都是这样的写法:

<子组件名称>

<子组件内容--标签 slot="插槽名"> </子组件内容--标签 >

</子组件名称>

注意:这里面在测试的时候出现了一个误区,我直接卸载了子组件名称的属性上,导致里面的内容也是无法正常显示。

这就比较难受,在vue3项目中使用vue2的老具名插槽用法不显示,原因可能有很多,保险起见,还是专门用脚手架建立的vue2项目中进行测试。

所以紧急来到上次做的vue2项目中,进行老语法测试:

父组件:index.vue

html 复制代码
<!--
 * @Author: RealRoad
 * @Date: 2024-11-12 09:25:23
 * @LastEditors: Do not edit
 * @LastEditTime: 2024-11-14 15:29:28
 * @Description: 
 * @FilePath: \datalized-crm-ui\datalized-crm-ui\src\views\test\index.vue
-->
<!--
 * @Author: RealRoad
 * @Date: 2024-11-12 09:25:23
 * @LastEditors: Do not edit
 * @LastEditTime: 2024-11-12 16:10:43
 * @Description: 
 * @FilePath: \datalized-crm-ui\datalized-crm-ui\src\views\test\index.vue
-->
<template>
    <div>
        <ComponentA ><div slot="top">测试一波具名插槽
          <img src="https://img1.baidu.com/it/u=1047145501,4073770646&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1731690000&t=14c402c69d53274bb7fa9af0d0e0e392" alt="">
        </div></ComponentA>
        <ComponentB />
        <!-- <a-form layout="inline" class="my-customer-form" @keyup.enter.native="searchQuery">
            <a-form-item label="商机名称">
              <a-input
                placeholder="请输入商机名称"
                
              >
              </a-input>
            </a-form-item>
            <a-form-item label="客户名称">
              <a-input
                placeholder="请输入客户名称"
                allowClear
               
              >
              </a-input>
            </a-form-item>
            
            <a-form-item label="赢单率">
              <a-select
                :getPopupContainer="node => node.parentNode"
                placeholder="请选择赢单率"
                default-value="10%"
                style="width: 100%"
                
                :style="{ width: searchItemWidth }"
              >
                <a-select-option value="10%"> 10%</a-select-option>
                <a-select-option value="20%"> 20%</a-select-option>
                <a-select-option value="30%"> 30%</a-select-option>
                <a-select-option value="40%"> 40%</a-select-option>
                <a-select-option value="50%"> 50%</a-select-option>
                <a-select-option value="60%"> 60%</a-select-option>
                <a-select-option value="70%"> 70%</a-select-option>
                <a-select-option value="80%"> 80%</a-select-option>
                <a-select-option value="90%"> 90%</a-select-option>
                <a-select-option value="100%"> 100%</a-select-option>
              </a-select>
            </a-form-item>
            <a-form-item label="商机状态">
              <j-dict-select-tag
                type="radioButton"
                
                
                dictCode="chance_status"
                
              />
            </a-form-item>
            
          </a-form>
          <hr>
          展示一下过度
          <div>
            <a-button type="primary" @click="isShow=!isShow">显示/隐藏</a-button>
            <transition name="mez" appear @afterEnter="handleEnter"
            @after-leave="handleLeave"
            @appear="handleAppear"
            @after-appear="myhandleEnter"
            @before-enter="myEnter"
            @enter="handleEnter"
            
            @leave="handleLeave">
            
                <h1 v-show="isShow" >测试文本</h1>
            </transition>
          </div>1112 -->
    </div>
</template>

<script>

import ComponentA from './brotherA.vue'
import ComponentB from './brotherB.vue'
import JDictSelectTag from '@/components/dict/JDictSelectTag'
export default {
    name: 'Test',
    data() {
        return {
            searchItemWidth:'200px',
            isShow:true
        };
    },
    methods: {
        handleEnter(){
            console.log('after-enter');
        },
        handleLeave(){
            console.log('after-leave');
        },
        handleAppear(){
            console.log('appear');
        },
        handleEnter(){
            console.log('enter');
        },
        handleLeave(){
            console.log('leave');
        },
        myEnter(){
            console.log('before-enter');
        },
        myhandleEnter(){
            console.log('after-appear');
        },

    },
    components: {
        ComponentA,
        ComponentB
    },

}
</script>

<style lang="less" scoped>
// @import '~@assets/less/common.less';

h1{
    background-color: rgb(98, 57, 133);
    
}
//进入的起点
.mez-enter,.mez-leave-to{
    transform: translateX(-100%);
}
//进入的过程
.mez-enter-active,.mez-leave-active{
    // animation: identifier 1s linear;
    transition: 0.5s linear;
}
// .mez-leave-active{
//     // animation: identifier 1s linear reverse;
// }
//进入的终点
.mez-enter-to,.mez-leave{
    transform: translateX(0);
}
//离开的起点
// .mez-leave{
//     transform: translateX(0);
// }
// //离开的终点
// .mez-leave-to{
//     transform: translateX(-100%);
// }


// @keyframes identifier {
//     from{
//         transform: translateX(-100%);
//     }
//     to{
//         transform: translateX(0px);
//     }
// }
</style>

子组件brotherA.vue:

html 复制代码
template>
<div>
 兄弟A组件
 <!-- <a-button type="primary" size="default" @click="handleClick">点我给B兄弟传值</a-button> -->

 <slot name="top">如果父组件没有内容显示我</slot>
</div>
</template>

<script>
export default {
 name: '',
 data() {
  return {
   
  };
 },
 methods: {
    // handleClick() {
    //   this.$emit('sendValue', '兄弟A组件传给B的参数');
    // }
  
 }
}
</script>

<style scoped>

</style>

来看效果:

果然和脚手架有关,可以正常使用。在vue3项目中哪怕是写成了不是setup语法糖的写法,也是不能正常显示,这里兄弟们需要注意一下,也有可能是我用vite建立的是vue3的项目,因为既有vue3的setup语法糖写法,又有vue2的export default{}写法,造成的冲突。【另外,这里还有一个vue2.7还是啥来着提出的配合template的<template v-slot:插槽名></template>的简写写法,这个就不展开说了,知道就行。】

③作用域插槽

它是一种特殊的插槽,允许子组件爱你将数据暴露给父组件的插槽内容。在子组件中,语法为<slot :数据名="数据值"></slot>的写法将自己的数据传递给插槽。

而在父组件中,通过<template v-slot:插槽名称="slotProps">接受数据,并使用slotProps来访问传递过来的数据。

子组件:

html 复制代码
<template>
  <div>
    <slot :users="userList"></slot>
  </div>
</template>

<script setup>
import { reactive } from 'vue';

// 定义一个响应式数组,作为作用域插槽的数据源
const userList = reactive([
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 30 },
  { name: 'Charlie', age: 35 }
]);
</script>

<style scoped>
/* 子组件的样式(如果需要的话) */
</style>

父组件:

html 复制代码
<template>
  <div>
    <h1>作用域插槽示例</h1>
    <ChildComponent>
      <!-- 使用 v-slot 接收作用域插槽的数据 -->
      <template #default="{ users }">
        <ul>
          <li v-for="user in users" :key="user.name">
            {{ user.name }} - {{ user.age }}
          </li>
        </ul>
      </template>
    </ChildComponent>
  </div>
</template>

<script setup>
import ChildComponent from './ChildComponent.vue';
</script>

<style scoped>
/* 父组件的样式(如果需要的话) */
</style>
相关推荐
OpenTiny社区2 分钟前
把 SearchBox 塞进项目,搜索转化率怒涨 400%?
前端·vue.js·github
编程猪猪侠31 分钟前
Tailwind CSS 自定义工具类与主题配置指南
前端·css
qhd吴飞35 分钟前
mybatis 差异更新法
java·前端·mybatis
YGY Webgis糕手之路1 小时前
OpenLayers 快速入门(九)Extent 介绍
前端·经验分享·笔记·vue·web
患得患失9491 小时前
【前端】【vueDevTools】使用 vueDevTools 插件并修改默认打开编辑器
前端·编辑器
ReturnTrue8681 小时前
Vue路由状态持久化方案,优雅实现记住表单历史搜索记录!
前端·vue.js
UncleKyrie1 小时前
一个浏览器插件帮你查看Figma设计稿代码图片和转码
前端
遂心_1 小时前
深入解析前后端分离中的 /api 设计:从路由到代理的完整指南
前端·javascript·api
你听得到111 小时前
Flutter - 手搓一个日历组件,集成单日选择、日期范围选择、国际化、农历和节气显示
前端·flutter·架构
风清云淡_A1 小时前
【REACT18.x】CRA+TS+ANTD5.X封装自定义的hooks复用业务功能
前端·react.js