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 组件,大幅提高开发效率和代码质量。

相关推荐
尾善爱看海7 小时前
Vue 面试收官篇:SSR、性能优化落地、30 道高频面试题精讲(附标准答案)
前端·javascript·vue.js·面试·vue
weixin_BYSJ19877 小时前
【计算机毕设】基于SpringBoot与Vue的文物保护档案管理系统08621
vue.js·spring boot·spring cloud·微服务·架构·django·课程设计
Bs_MoneyMagnet8 小时前
基于springboot+vue的个人健康管理系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·vue3·springboot3·计算机毕业设计
驳是8 小时前
一个组件,提升 react-router 开发的幸福感
前端·preact
GreenTea8 小时前
OpenAI Agents API 上手实测:一次调用把整个 agent loop 甩给 OpenAI
前端·后端·算法
星云API技术支持8 小时前
企业微信二次开发:群权限设置、成员管理与群资料维护的接口组合实践
java·前端·企业微信
京东云开发者9 小时前
81.8 秒的视频,我们改了 15 个版本:一次纯 Codex 驱动的 AI-native 视频实践
前端·aigc
Csvn9 小时前
TypeScript 大型项目架构:从单体 tsconfig 到分层可扩展的工程
前端
Bs_MoneyMagnet11 小时前
基于springboot+vue的心理咨询预约与随访平台的设计与实现 源码+文档
vue.js·spring boot·后端·spring·毕业设计·旅游·计算机毕业设计
计算机魔术师11 小时前
Dario Amodei 发文呼吁为前沿 AI 降速并提出三点计划
前端