源本科技 | 码上会

Vue3 slots 插槽

2026/04/23
19
0

引言

Slots(插槽)是 Vue 提供的内容分发机制,用于实现父组件向子组件传递自定义 HTML 结构、文本或组件,极大提升组件的灵活性和复用性

基础概念

插槽的核心作用:子组件定义占位位置,父组件填充实际内容,完美解耦组件结构与业务内容,是组件化开发的必备技能。

默认插槽

默认插槽是最基础的插槽形式,无需命名,子组件用 <slot> 定义占位,父组件直接在组件标签内书写内容即可渲染。

<!-- 子组件 Child.vue -->
<template>
  <div class="child-box">
    <h3>子组件固定内容</h3>
    <!-- 默认插槽 -->
    <slot>默认占位内容(父组件不传值时显示)</slot>
  </div>
</template>

<!-- 
  父组件 Parent.vue
  1. 在 App.vue 中 import 观察效果
  2. 删除 <Child> 中的全部内容再观察效果
-->
<template>
  <Child>
    <!-- 内容自动插入默认插槽 -->
    <p>这是父组件传入的自定义内容</p>
  </Child>
</template>

<script setup lang="ts">
import Child from './Child.vue'
</script>

具名插槽

当组件需要多个插槽位置时,使用具名插槽。子组件通过 name 属性定义插槽名称,父组件通过 #插槽名v-slot 简写)指定内容插入位置。

<!-- 子组件 Child.vue -->
<template>
  <div class="child-box">
    <!-- 具名插槽:头部 -->
    <header>
      <slot name="header"></slot>
    </header>
    <!-- 默认插槽:主体 -->
    <main>
      <slot></slot>
    </main>
    <!-- 具名插槽:底部 -->
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

<!-- 父组件 Parent.vue -->
<template>
  <Child>
    <template #header>
      <h4>自定义头部</h4>
    </template>
    <!-- 无模板标签 = 默认插槽 -->
    <p>自定义主体内容</p>
    <template #footer>
      <span>自定义底部</span>
    </template>
  </Child>
</template>

<script setup lang="ts">
import Child from './Child.vue'
</script>

作用域插槽

作用域插槽可以让子组件向父组件传递数据,父组件能使用子组件内部的数据渲染插槽内容,是高阶插槽用法。

<!-- 子组件 List.vue -->
<template>
  <ul>
    <li v-for="item in list" :key="item.id">
      <!-- 子组件通过 v-bind 向插槽传递数据 -->
      <slot :row="item" :index="item.id"></slot>
    </li>
  </ul>
</template>

<script setup lang="ts">
import { ref } from 'vue'
// 子组件内部数据
const list = ref([
  { id: 1, name: 'Vue 3 插槽' },
  { id: 2, name: 'Vue 3 组件通信' }
])
</script>

<!-- 父组件 Parent.vue -->
<template>
  <List>
    <!-- 解构子组件传递的数据 -->
    <template #default="{ row }">
      <span>{{ row.name }}</span>
    </template>
  </List>
</template>

<script setup lang="ts">
import List from './List.vue'
</script>

为组件集成具名插槽

基于我们之前开发的 MyText 组件,保留 Props、自定义事件、双向绑定 功能,新增后缀具名插槽suffix),实现输入框后置自定义内容(如 元、个、次 等单位)。

子组件改造

修改 src/components/MyText/src/index.vue

<template>
  <div class="my-text-wrapper">
    <label class="my-label" for="text-input">{{ label }}:</label>
    <input
      id="text-input"
      type="text"
      class="my-input-text"
      :value="modelValue"
      @input="handleInput"
      :placeholder="placeholder"
      :disabled="disabled"
    />
    <!-- 具名插槽:suffix 后缀,支持父组件自定义内容 -->
    <slot name="suffix" class="suffix-text">(请填写单位)</slot>
    <button class="submit-btn" @click="handleSubmit">提交</button>
  </div>
</template>

<script setup lang="ts">
// 编译时宏,无需手动导入
// 定义 Props
const props = defineProps({
  label: { type: String, required: true },
  placeholder: { type: String, default: '请输入内容' },
  disabled: { type: Boolean, default: false },
  modelValue: { type: String, default: '' }
})

// 定义自定义事件
const emit = defineEmits<{
  'update:modelValue': [value: string]
  'submit': [label: string, value: string]
}>()

// 输入框双向绑定
const handleInput = (e: Event) => {
  const value = (e.target as HTMLInputElement).value
  emit('update:modelValue', value)
}

// 提交事件
const handleSubmit = () => {
  emit('submit', props.label, props.modelValue)
}
</script>

<style scoped>
.my-text-wrapper {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 12px 0;
}

.my-label {
  font-size: 14px;
  color: #333;
}

.my-input-text {
  padding: 8px 12px;
  border-radius: 4px;
  border: 1px solid #ccc;
  outline: none;
}

.suffix-text {
  font-size: 14px;
  color: #666;
}

.submit-btn {
  padding: 8px 16px;
  border-radius: 4px;
  border: none;
  background-color: #409eff;
  color: #fff;
  cursor: pointer;
}
</style>

父组件使用插槽

在父组件中,通过 #suffix 使用具名插槽,自定义输入框后置内容,保持组件功能连贯:

<template>
  <div class="demo-box">
    <h3>MyText 组件 - 插槽实战</h3>
    
    <!-- 1. 不使用插槽:显示默认插槽内容 -->
    <MyText label="商品数量" v-model="count" />

    <!-- 2. 使用具名插槽 suffix:自定义后置内容 -->
    <MyText label="支付金额" v-model="price" @submit="handleSubmit">
      <template #suffix>
        <span class="unit"> 元 </span>
      </template>
    </MyText>
  </div>
</template>

<script setup lang="ts">
import { MyText } from './components/MyText'
import { ref } from 'vue'

// 响应式数据
const count = ref('')
const price = ref('')

// 监听提交事件
const handleSubmit = (label: string, value: string) => {
  alert(`${label}:${value}`)
}
</script>

<style scoped>
.demo-box {
  padding: 20px;
}
.unit {
  color: #f56c6c;
  font-weight: bold;
}
</style>

知识点总结

语法规范

  1. 默认插槽:子组件 <slot>,父组件直接书写内容

  2. 具名插槽:子组件 <slot name="xxx">,父组件 <template #xxx>

  3. 作用域插槽:子组件 <slot :数据名="值">,父组件解构使用 #default="{ 数据名 }"

关键特性

  1. 插槽支持默认内容:父组件不传值时,显示子组件定义的默认文本

  2. 插槽可以传递任意内容:文本、HTML 标签、其他 Vue 组件

  3. 严格遵循 Vue 3 语法,#v-slot 的官方简写,优先级最高

  4. 与 Props、自定义事件配合,构建高复用、高灵活的企业级组件

总结

  1. 插槽是 Vue 内容分发的核心,分为默认、具名、作用域三种形式

  2. 具名插槽适合多区域分发内容,作用域插槽支持子向父传数据

  3. 实战中为 MyText 增加插槽后,组件适配更多业务场景,复用性大幅提升

  4. Vue 3 中 # 简写语法是标准用法,简洁且易于维护