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

相关推荐
胡斌附体20 分钟前
vue添加loading后修复页面渲染问题
前端·javascript·vue.js·渲染·v-if·异步加载
酷爱码1 小时前
css中的 vertical-align与line-height作用详解
前端·css
沐土Arvin1 小时前
深入理解 requestIdleCallback:浏览器空闲时段的性能优化利器
开发语言·前端·javascript·设计模式·html
专注VB编程开发20年1 小时前
VB.NET关于接口实现与简化设计的分析,封装其他类
java·前端·数据库
小妖6662 小时前
css 中 content: “\e6d0“ 怎么变成图标的?
前端·css
L耀早睡2 小时前
mapreduce打包运行
大数据·前端·spark·mapreduce
MaCa .BaKa3 小时前
38-日语学习小程序
java·vue.js·spring boot·学习·mysql·小程序·maven
HouGISer3 小时前
副业小程序YUERGS,从开发到变现
前端·小程序
outstanding木槿3 小时前
react中安装依赖时的问题 【集合】
前端·javascript·react.js·node.js
霸王蟹3 小时前
React中useState中更新是同步的还是异步的?
前端·javascript·笔记·学习·react.js·前端框架