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

相关推荐
Mintopia几秒前
计算机图形学学习指南
前端·javascript·计算机图形学
Mintopia1 分钟前
three.js 中的动画(animation)
前端·javascript·three.js
AI大模型顾潇3 分钟前
[特殊字符] Prompt如何驱动大模型对本地文件实现自主变更:Cline技术深度解析
前端·人工智能·llm·微调·prompt·编程·ai大模型
苹果酱05679 分钟前
Vue3 源码解析(六):响应式原理与 reactive
java·vue.js·spring boot·mysql·课程设计
小小小小宇14 分钟前
React中 useEffect和useLayoutEffect源码原理
前端
AlexJee16 分钟前
在vue3中使用vue-cropper完成头像裁剪上传图片功能
前端
清晨細雨18 分钟前
uniapp微信小程序:WIFI设备配网之TCP/UDP开发AP配网
前端·物联网·小程序·uni-app
阿廖沙102418 分钟前
Rust核心概念
前端
阿廖沙102421 分钟前
🚀 从“值放哪了”聊起:Rust 内存管理通透讲解(适合前端工程师)
前端