vue3

插槽详细说明

# 1. Vue 插槽(Slots)

# 1.1. 一、为什么需要插槽?

在开发组件时,props 只能传递数据,但很多场景下我们需要传递模板内容。

比如一个按钮组件,我们希望它有统一的样式,但内部文字或图标由外部决定。这时就用到插槽(slot)。

你可以把插槽理解为:组件的“占位符”,由父组件决定填充的内容。

# 1.2. 二、插槽的基础用法

# 1.2.1. 默认插槽

父组件使用:

<!-- 父组件 -->
<FancyButton>
  点我一下! <!-- 插槽内容 -->
</FancyButton>

子组件 FancyButton.vue

<template>
  <button class="fancy-btn">
    <!-- 插槽出口,父组件传什么,这里就显示什么 -->
    <slot></slot>
  </button>
</template>

渲染结果:

<button class="fancy-btn">点我一下!</button>

这样 FancyButton 的样式固定,但内容灵活。

# 1.2.2. 默认内容

如果父组件没有传递内容,可以给插槽设置默认值。

子组件 SubmitButton.vue

<template>
  <button type="submit">
    <slot>提交</slot>
    <!-- 默认是“提交” -->
  </button>
</template>

父组件:

<SubmitButton />
<!-- 没传内容 -->
<SubmitButton>保存</SubmitButton>
<!-- 传了内容 -->

结果:

<button type="submit">提交</button> <button type="submit">保存</button>

# 1.2.3. 具名插槽

当组件内部有多个位置需要插入不同内容时:

BaseLayout.vue

<template>
  <div class="container">
    <header><slot name="header"></slot></header>
    <main><slot></slot></main>
    <!-- 默认插槽 -->
    <footer><slot name="footer"></slot></footer>
  </div>
</template>

父组件使用:

<BaseLayout>
  <template #header>
    <h1>我是标题</h1>
  </template>

  <p>正文内容...</p> <!-- 默认插槽 -->

  <template #footer>
    <p>我是底部信息</p>
  </template>
</BaseLayout>

渲染结果示意:

+--------------------+
| 我是标题           |
+--------------------+
| 正文内容...        |
+--------------------+
| 我是底部信息       |
+--------------------+

具名插槽就是给“占位符”贴标签,方便对应填充。

# 1.2.4. 条件插槽

如果某个插槽内容没有传递,可以通过 $slots 判断是否需要渲染:

父组件传 header:

<Card>
  <template #header>头部</template>
</Card>

Card.vue

<template>
  <div class="card">
    <div v-if="$slots.header"><slot name="header" /></div>
    <div v-if="$slots.default"><slot /></div>
    <div v-if="$slots.footer"><slot name="footer" /></div>
  </div>
</template>

# 1.2.5. 动态插槽名

插槽名可动态绑定:

<base-layout>
  <template v-slot:[dynamicSlot]>
    动态内容
  </template>
</base-layout>

# 1.3. 三、作用域插槽(Scoped Slots)

默认情况下,插槽内容只能访问父组件的数据。但有时我们希望子组件把一些数据“传出来”,让父组件在插槽里使用。

子组件示例:

<!-- MyComponent.vue -->
<template>
  <slot :text="greeting" :count="1"></slot>
</template>

<script setup>
const greeting = '你好'
</script>

父组件使用:

<MyComponent v-slot="{ text, count }">
  {{ text }} - 次数:{{ count }}
</MyComponent>

渲染结果:

你好 - 次数:1

类比 JavaScript:

function MyComponent(slotFn) {
  const data = { text: '你好', count: 1 }
  return slotFn(data) // 调用函数并传入参数
}

所以作用域插槽可以理解为:子组件给父组件“回调函数”传参。

# 1.3.1. 具名作用域插槽

<MyComponent>
  <template #header="{ message }">
    <h2>{{ message }}</h2>
  </template>
</MyComponent>

子组件:

<slot name="header" :message="'来自子组件的数据'"></slot>

# 1.4. 四、实战案例

# 1.4.1. 高级列表组件

FancyList.vue

<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      <!-- 把 item 数据传给插槽 -->
      <slot name="item" v-bind="item"></slot>
    </li>
  </ul>
</template>

<script setup>
import { ref } from 'vue'
const items = ref([
  { id: 1, text: 'Vue', likes: 100 },
  { id: 2, text: 'React', likes: 200 }
])
</script>

父组件:

<FancyList>
  <template #item="{ text, likes }">
    <p>{{ text }} ❤️ {{ likes }}</p>
  </template>
</FancyList>

渲染结果:

Vue ❤️ 100
React ❤️ 200

# 1.4.2. 无渲染组件(逻辑组件)

封装逻辑但不渲染视图,内容交给父组件:

MouseTracker.vue

<template>
  <slot :x="x" :y="y"></slot>
</template>

<script setup>
import { ref, onMounted } from 'vue'
const x = ref(0),
  y = ref(0)
onMounted(() => {
  window.addEventListener('mousemove', e => {
    x.value = e.pageX
    y.value = e.pageY
  })
})
</script>

父组件:

<MouseTracker v-slot="{ x, y }">
  鼠标位置:{{ x }} , {{ y }}
</MouseTracker>

# 1.5. 五、知识点图解

父组件 ----> 子组件
   │           │
   │ props     │
   │ slot内容  │ <slot> 占位符
   ▼           ▼
插槽渲染 = 父组件内容 + 子组件外壳

# 1.6. 六、总结

  • 默认插槽:没有名字的插槽,占位填充内容。
  • 默认内容:父组件没传时,slot 会显示默认值。
  • 具名插槽:给 slot 命名,实现多出口填充。
  • 条件插槽:通过 $slots 判断是否存在内容。
  • 动态插槽名:slot 的名字可动态绑定。
  • 作用域插槽:子组件把数据传给插槽,让父组件使用。
  • 实战场景:列表组件、无渲染组件,逻辑和视图分离。

插槽的核心价值:让组件更灵活、可复用,同时保留样式与逻辑的封装性。

上次更新: