Vue 插槽(Slot)详解

插槽是 Vue 组件系统中极其强大的功能,它允许组件在定义时预留"缺口",让使用组件的父级可以灵活地插入自定义内容。下面我将从基础到高级全面讲解 Vue 插槽的各个方面。

一、插槽的核心概念

1. 为什么需要插槽

假设我们要创建一个按钮组件:

vue 复制代码
<!-- 简单按钮组件 -->
<template>
  <button class="my-btn">按钮</button>
</template>

这个按钮的文本是固定的"按钮",如果想在不同地方使用时显示不同文本,常规做法是通过 props:

vue 复制代码
<my-button text="提交"></my-button>

但当需要插入复杂内容(如图标+文字组合)时,props 就显得力不从心。这时就需要插槽

2. 插槽的基本原理

插槽相当于组件内部的"占位符",父组件可以在使用子组件时,将任意内容"注入"到这些占位符中。

二、插槽类型详解

1. 默认插槽(匿名插槽)

基本用法

vue 复制代码
<!-- 子组件 Child.vue -->
<template>
  <div class="child">
    <h3>子组件标题</h3>
    <slot>默认内容(父组件不提供内容时显示)</slot>
  </div>
</template>

<!-- 父组件使用 -->
<child>
  <p>这是父组件插入的内容</p>
</child>

特点

  • 一个组件可以有且只有一个默认插槽
  • 当父组件不提供内容时,会显示 <slot> 标签内的默认内容
  • 所有未包裹在 <template v-slot> 中的内容都会被视为默认插槽的内容

2. 具名插槽

当需要多个插槽时,可以为插槽命名:

vue 复制代码
<!-- 子组件 Layout.vue -->
<template>
  <div class="layout">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot></slot>  <!-- 默认插槽 -->
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

<!-- 父组件使用 -->
<layout>
  <template v-slot:header>
    <h1>页面标题</h1>
  </template>

  <p>这是主要内容...</p>  <!-- 默认插槽内容 -->

  <template v-slot:footer>
    <p>© 2023 公司名称</p>
  </template>
</layout>

具名插槽缩写语法

Vue 2.6.0+ 支持使用 # 代替 v-slot:

vue 复制代码
<template #header>
  <h1>页面标题</h1>
</template>

3. 作用域插槽

作用域插槽允许子组件将数据传递给插槽内容,实现"子传父"的数据流。

基本用法

vue 复制代码
<!-- 子组件 UserList.vue -->
<template>
  <ul>
    <li v-for="user in users" :key="user.id">
      <slot :user="user" :index="index"></slot>
    </li>
  </ul>
</template>

<script>
export default {
  data() {
    return {
      users: [
        { id: 1, name: '张三' },
        { id: 2, name: '李四' }
      ]
    }
  }
}
</script>

<!-- 父组件使用 -->
<user-list>
  <template v-slot:default="slotProps">
    <span>{{ slotProps.user.name }}</span> - 
    <span>序号: {{ slotProps.index }}</span>
  </template>
</user-list>

解构语法

vue 复制代码
<user-list>
  <template v-slot:default="{ user, index }">
    {{ index + 1 }}. {{ user.name }}
  </template>
</user-list>

具名作用域插槽

vue 复制代码
<!-- 子组件 -->
<slot name="item" :item="itemData" :index="itemIndex"></slot>

<!-- 父组件使用 -->
<template #item="{ item, index }">
  <div>{{ index }} - {{ item.name }}</div>
</template>

三、高级插槽技巧

1. 动态插槽名

vue 复制代码
<template>
  <base-layout>
    <template v-slot:[dynamicSlotName]>
     动态插槽内容
    </template>
  </base-layout>
</template>

<script>
export default {
  data() {
    return {
      dynamicSlotName: 'header' // 可以动态改变
    }
  }
}
</script>

2. 插槽组合使用

vue 复制代码
<!-- 一个复杂的卡片组件 -->
<template>
  <div class="card">
    <div class="card-header">
      <slot name="header">
        <h3>{{ title }}</h3>
      </slot>
    </div>
    <div class="card-body">
      <slot></slot>
    </div>
    <div class="card-footer">
      <slot name="footer">
        <button @click="$emit('close')">关闭</button>
      </slot>
    </div>
  </div>
</template>

3. 无渲染组件(Renderless Components)

利用作用域插槽创建只处理逻辑不渲染内容的组件:

vue 复制代码
<!-- 鼠标位置追踪组件 -->
<template>
  <slot :x="x" :y="y"></slot>
</template>

<script>
export default {
  data() {
    return {
      x: 0,
      y: 0
    }
  },
  methods: {
    updatePosition(e) {
      this.x = e.clientX
      this.y = e.clientY
    }
  },
  mounted() {
    window.addEventListener('mousemove', this.updatePosition)
  },
  beforeDestroy() {
    window.removeEventListener('mousemove', this.updatePosition)
  }
}
</script>

<!-- 使用 -->
<mouse-tracker v-slot="{ x, y }">
  鼠标位置: {{ x }}, {{ y }}
</mouse-tracker>

四、插槽的底层原理

1. slots和slots和scopedSlots

  • this.$slots: 访问静态插槽内容

    javascript 复制代码
    // 检查默认插槽是否有内容
    if (this.$slots.default) {
      // ...
    }
  • this.$scopedSlots: 访问作用域插槽函数

    javascript 复制代码
    // 调用作用域插槽并传递数据
    const slotContent = this.$scopedSlots.default({
      user: this.userData
    })

2. 渲染函数中的插槽

在渲染函数中直接操作插槽:

javascript 复制代码
export default {
  render(h) {
    return h('div', [
      // 默认插槽内容
      this.$slots.default,
      
      // 具名插槽
      this.$slots.header,
      
      // 作用域插槽
      this.$scopedSlots.footer({
        text: '页脚文本'
      })
    ])
  }
}

五、插槽的最佳实践

  1. 命名规范 :使用 kebab-case 命名具名插槽,如 header-left

  2. 合理使用默认内容

    vue 复制代码
    <slot name="actions">
      <button @click="$emit('cancel')">取消</button>
      <button @click="$emit('submit')">提交</button>
    </slot>
  3. 避免插槽嵌套过深:超过3层插槽嵌套会降低可维护性

  4. 文档化插槽:使用 JSDoc 或专用文档工具记录插槽用途

    javascript 复制代码
    /**
     * @slot header - 卡片头部内容
     * @slot default - 卡片主要内容
     * @slot footer - 卡片页脚,接收 { close } 参数
     */
  5. 性能考虑 :作用域插槽会在父组件重新渲染时触发更新,必要时使用 v-once

六、Vue 3 中的插槽变化

  1. 统一 $slots$scopedSlots :Vue 3 中只有 $slots,所有插槽都是函数

  2. v-slot 简写 :可以更简洁地使用 # 符号

    vue 复制代码
    <template #item="{ data }">
      {{ data.name }}
    </template>
  3. 片段支持:组件可以有多个根节点,插槽内容可以分布在多个位置

  4. 组合式 API 中的插槽

    javascript 复制代码
    import { useSlots } from 'vue'
    
    export default {
      setup() {
        const slots = useSlots()
        // 检查插槽是否存在
        const hasFooter = !!slots.footer
        return { hasFooter }
      }
    }

通过深入理解和灵活运用插槽,你可以创建出高度灵活、可复用的 Vue 组件,大幅提高开发效率和代码质量。

相关推荐
中微子1 小时前
React状态管理最佳实践
前端
烛阴1 小时前
void 0 的奥秘:解锁 JavaScript 中 undefined 的正确打开方式
前端·javascript
中微子1 小时前
JavaScript 事件与 React 合成事件完全指南:从入门到精通
前端
Hexene...2 小时前
【前端Vue】如何实现echarts图表根据父元素宽度自适应大小
前端·vue.js·echarts
初遇你时动了情2 小时前
腾讯地图 vue3 使用 封装 地图组件
javascript·vue.js·腾讯地图
华子w9089258592 小时前
基于 SpringBoot+VueJS 的农产品研究报告管理系统设计与实现
vue.js·spring boot·后端
天天扭码2 小时前
《很全面的前端面试题》——HTML篇
前端·面试·html
xw52 小时前
我犯了错,我于是为我的uni-app项目引入环境标志
前端·uni-app
!win !2 小时前
被老板怼后,我为uni-app项目引入环境标志
前端·小程序·uni-app
Burt2 小时前
tsdown vs tsup, 豆包回答一坨屎,还是google AI厉害
前端