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

相关推荐
Lepusarcticus7 分钟前
《掌握 JavaScript 字符串操作,这一篇就够了!》
前端·javascript
田本初12 分钟前
vue-cli工具build测试与生产包对css处理的不同
前端·css·vue.js
inxunoffice1 小时前
批量在多个 PDF 的指定位置插入页,如插入封面、插入尾页
前端·pdf
木木黄木木1 小时前
HTML5 Canvas绘画板项目实战:打造一个功能丰富的在线画板
前端·html·html5
豆芽8191 小时前
基于Web的交互式智能成绩管理系统设计
前端·python·信息可视化·数据分析·交互·web·数据可视化
不是鱼1 小时前
XSS 和 CSRF 为什么值得你的关注?
前端·javascript
顺遂时光1 小时前
微信小程序——解构赋值与普通赋值
前端·javascript·vue.js
anyeyongzhou1 小时前
img标签请求浏览器资源携带请求头
前端·vue.js
Captaincc1 小时前
腾讯云 EdgeOne Pages「MCP Server」正式发布
前端·腾讯·mcp
最新资讯动态2 小时前
想让鸿蒙应用快的“飞起”,来HarmonyOS开发者官网“最佳实践-性能专区”
前端