Vue插槽

文章目录

  • [Vue 插槽详解:默认插槽、具名插槽与作用域插槽](#Vue 插槽详解:默认插槽、具名插槽与作用域插槽)
    • 什么是插槽
    • 一、默认插槽
      • [1. 子组件先占位](#1. 子组件先占位)
      • [2. 父组件传入内容](#2. 父组件传入内容)
      • [3. 一个容易混淆的点](#3. 一个容易混淆的点)
    • 二、具名插槽
      • [1. 子组件定义多个具名插槽](#1. 子组件定义多个具名插槽)
      • [2. 父组件用 `template + #插槽名` 分发内容](#插槽名` 分发内容)
      • [3. 默认插槽的具名写法](#3. 默认插槽的具名写法)
    • 三、作用域插槽
      • [1. 子组件把数据绑定到 slot 上](#1. 子组件把数据绑定到 slot 上)
      • [2. 父组件用 `#插槽名="scope"` 接收](#插槽名="scope"` 接收)
      • [3. 作用域插槽的典型应用](#3. 作用域插槽的典型应用)
    • 四、动态插槽名(进阶)
    • 五、常见坑
    • 小结

Vue 插槽详解:默认插槽、具名插槽与作用域插槽

写组件时经常遇到这样的需求:组件的外壳和布局是固定的,但里面的内容得由使用者决定。用 props 传字符串能应付简单场景,可一旦要传 HTML 结构、图片甚至一整段交互逻辑,props 就不够用了。这时候该 插槽(slot)上场。

什么是插槽

插槽可以理解为组件里预留的"坑位":子组件负责挖坑(写 <slot>),父组件负责填坑(在组件标签内部写内容)。

它解决的核心问题是:让组件的结构可复用,同时让内容可定制。

一、默认插槽

最简单的插槽,一个组件只有一个坑,没有名字。

1. 子组件先占位

AAA.vue(折叠面板组件)里,原本写死了四句诗:

xml 复制代码
<template>
  <div class="container" v-show="visible">
    <p>生命诚可贵,</p>
    <p>爱情价更高。</p>
    <p>若为自由故,</p>
    <p>两者皆可抛。</p>
  </div>
</template>

把写死的内容换成 <slot>,就变成了一个可复用的容器:

xml 复制代码
<template>
  <div class="container" v-show="visible">
    <slot>默认值</slot>
  </div>
</template>

<slot> 标签内的内容就是默认内容------当父组件没有传入任何内容时,显示它。

2. 父组件传入内容

App.vue 里使用 AAA 组件,并在标签内部填写内容:

xml 复制代码
<template>
  <h3>折叠面板</h3>

  <!-- 展示一首诗 -->
  <AAA>
    <p>生命诚可贵,</p>
    <p>爱情价更高。</p>
    <p>若为自由故,</p>
    <p>两者皆可抛。</p>
  </AAA>

  <!-- 展示 2 张图片 -->
  <AAA>
    <div class="img-box">
      <img width="100%" src="./assets/lq.png" alt="lq" />
      <img width="100%" src="./assets/sy.png" alt="sy" />
    </div>
  </AAA>
</template>

同一个 AAA 组件,第一次装了一首诗,第二次装了两张图。组件结构没变,内容完全由调用方决定。

3. 一个容易混淆的点

xml 复制代码
<!-- 这种写法是父给子传参数(props) -->
<AAA title="标题" :list="data" />

<!-- 这种写法是插槽,用来替换 <slot></slot> -->
<AAA>
  <p>这里是插槽内容</p>
</AAA>

区别很直观:写在标签属性里的是 props,写在标签内部的是插槽内容。

二、具名插槽

当一个组件需要多个坑位时(比如头部、主体、底部),就得给每个 <slot> 起个名字。

1. 子组件定义多个具名插槽

xml 复制代码
<!-- Card.vue -->
<template>
  <div class="card">
    <header>
      <slot name="header">默认标题</slot>
    </header>

    <main>
      <!-- 没有 name 的就是默认插槽 -->
      <slot>默认内容</slot>
    </main>

    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

要点:

  • name="header" 这种就是具名插槽
  • 不带 name 的 <slot> 等价于 name="default",即默认插槽
  • 每个插槽都可以有自己的默认内容

2. 父组件用 template + #插槽名 分发内容

xml 复制代码
<!-- App.vue -->
<template>
  <Card>
    <template #header>
      <h2>用户信息</h2>
    </template>

    <!-- 没有指定名字的内容会进入默认插槽 -->
    <p>这是主体内容</p>

    <template #footer>
      <button>确定</button>
      <button>取消</button>
    </template>
  </Card>
</template>

#header 是 v-slot:header 的简写,完整写法是:

xml 复制代码
<template v-slot:header>...</template>
<!-- 等价于 -->
<template #header>...</template>

3. 默认插槽的具名写法

默认插槽也可以显式写出来:

xml 复制代码
<Card>
  <template #default>
    <p>主体内容</p>
  </template>
</Card>

注意是 #default,不是单独一个 #。

三、作用域插槽

前面两种插槽都是父组件往子组件里塞内容,数据流向是"父 → 子"。但有时需求反过来:子组件手里有数据,想让父组件来决定怎么渲染。

这就是作用域插槽 :子组件通过 <slot> 把数据"暴露"出去,父组件接收后自行决定展示方式。

1. 子组件把数据绑定到 slot 上

xml 复制代码
<!-- List.vue -->
<script setup>
defineProps({
  list: {
    type: Array,
    default: () => []
  }
})
</script>

<template>
  <ul class="list">
    <li v-for="(item, index) in list" :key="index">
      <!-- 把 item 和 index 传给父组件 -->
      <slot :item="item" :index="index">
        {{ item.name }}
      </slot>
    </li>
  </ul>
</template>

:item="item" 和 :index="index" 就是暴露给父组件的数据。如果父组件不传内容,就走默认显示 {``{ item.name }}。

2. 父组件用 #插槽名="scope" 接收

xml 复制代码
<!-- App.vue -->
<script setup>
import { reactive } from 'vue'

const users = reactive([
  { name: '张三', age: 20, city: '北京' },
  { name: '李四', age: 25, city: '上海' }
])
</script>

<template>
  <List :list="users">
    <template #default="{ item, index }">
      <span class="row">
        {{ index + 1 }}. {{ item.name }}({{ item.age }} 岁,来自{{ item.city }})
      </span>
    </template>
  </List>
</template>

几点说明:

  • #default="scope" 中的 scope 是一个对象,包含子组件传过来的所有属性,这里是 { item, index }
  • 直接解构 { item, index } 更简洁,推荐这种写法
  • 换一个父组件场景,同一个 List 可以渲染成表格、卡片、树形结构,渲染逻辑完全由父组件决定

3. 作用域插槽的典型应用

  • 表格组件:列的内容由使用者自定义
  • 下拉选择组件:选项的显示样式由使用者决定
  • 列表组件:每一项的渲染方式由使用者控制
  • 图表库封装:数据项如何展示由调用方定义

四、动态插槽名(进阶)

插槽名也可以是变量:

xml 复制代码
<template>
  <Card>
    <template #[dynamicSlotName]>
      <p>动态内容</p>
    </template>
  </Card>
</template>

<script setup>
import { ref } from 'vue'
const dynamicSlotName = ref('header')
</script>

适合在配置化组件里根据配置项决定往哪个插槽塞内容。

五、常见坑

  • v-slot 只能用在 <template> 或组件标签上 :不能写在普通 HTML 元素(如 div)上,否则无效。
  • 简写 # 后面不能有空格 :#header 合法,# header 会报错。
  • 插槽内容的编译作用域在父组件 :父组件模板里的所有内容都在父组件作用域编译,因此只能访问父组件的数据,不能直接访问子组件的数据。要拿子组件的数据,必须用作用域插槽。
  • 默认插槽的简写是 #default :不是单独一个 #。
  • 多个默认插槽会警告:一个组件里只能有一个默认插槽,其余必须具名。
  • 插槽内容是响应式的:父组件的数据变化后,插槽内容也会跟着更新,不用额外处理。

小结

三种插槽,一句话记住:

类型 特点 典型场景
默认插槽 一个坑,谁用谁填 通用容器、折叠面板
具名插槽 多个坑,按名字对号入座 卡片、对话框、布局组件
作用域插槽 子组件挖坑并提供数据,父组件决定怎么展示 列表、表格、下拉框
相关推荐
qq_448011162 小时前
Vue + Jinja2两套模版与法冲突造成undefinedError
前端·javascript·vue.js
aixingpan2 小时前
aixingpan.cn API开发文档:前端查询夏令时和时区接口指南
前端·php
2601_958352902 小时前
23.5×19mm 换掉一整条音频链?|A-68 集成了什么:ADC / DAC / 24.576MHz 时钟 / AEC / 波束降噪,主控仅需 6 根线
前端·音视频·ai降噪·回声消除·语音模块
志尊宝3 小时前
AI原生前端开发(039):AI辅助的Nuxt 4全栈开发
前端·vue.js·ai-native
FRing034 小时前
JavaScript入门到精通三十二(深拷贝、浅拷贝、递归函数)
开发语言·前端·javascript
excel4 小时前
双 Token 与前端使用 Crypto 公私钥鉴权换 Token,哪种方案更好?
前端·后端
外收内放4 小时前
Python基础语法练习题(73)
java·前端·python
风骏时光牛马5 小时前
面向复杂场景的大模型应用落地与能力优化实践
前端
小小善后师5 小时前
我做了个病历归档小程序,被 AI 识别的"最后一公里"狠狠上了一课
前端