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>
相关推荐
初遇你时动了情12 分钟前
react 项目打包二级目 使用BrowserRouter 解决页面刷新404 找不到路由
前端·javascript·react.js
乔峰不是张无忌33031 分钟前
【HTML】动态闪烁圣诞树+雪花+音效
前端·javascript·html·圣诞树
码农老起38 分钟前
掌握 React:组件化开发与性能优化的实战指南
react.js·前端框架
鸿蒙自习室38 分钟前
鸿蒙UI开发——组件滤镜效果
开发语言·前端·javascript
m0_748250741 小时前
高性能Web网关:OpenResty 基础讲解
前端·openresty
前端没钱1 小时前
从 Vue 迈向 React:平滑过渡与关键注意点全解析
前端·vue.js·react.js
汪洪墩1 小时前
【Mars3d】设置backgroundImage、map.scene.skyBox、backgroundImage来回切换
开发语言·javascript·python·ecmascript·webgl·cesium
NoneCoder1 小时前
CSS系列(29)-- Scroll Snap详解
前端·css
无言非影1 小时前
vtie项目中使用到了TailwindCSS,如何打包成一个单独的CSS文件(优化、压缩)
前端·css
我曾经是个程序员2 小时前
鸿蒙学习记录
开发语言·前端·javascript