34.Vue 插槽(Slot)终极学习比较指南默认插槽、默认插槽、具名插槽、作用域插槽的详细建设当子组件有多个位置需要透传数据时,父组件就写多个 <template #名字="数据对象">,各管各的,互不打架。

Vue 插槽(Slot)终极学习比较指南

这是一份从入门到精通的 Vue 插槽全景对比,帮你彻底理清所有概念。


一、核心概念总览表

 

插槽类型

别名

核心作用

数据流向

难度

默认插槽​

匿名插槽

父传子(结构)

父 → 子

⭐

具名插槽​

多插槽

精准投放内容

父 → 子

⭐⭐

作用域插槽​

带数据的插槽

子传父(数据)

子 → 父

⭐⭐⭐⭐

后备内容​

默认值

保底显示

无

⭐


二、详细对比分析

1️⃣ 默认插槽(最基础)

子组件 (Child.vue)

<template>
  <div class="card">
    <!-- 挖个坑 -->
    <slot></slot>
  </div>
</template>

父组件 (Parent.vue)

<template>
  <Child>
    <!-- 填坑(不需要 v-slot) -->
    <p>我是父组件的内容</p>
  </Child>
</template>

✅ 特点:

  • 最简单

  • 只能有一个默认坑

  • 父组件内容直接用


2️⃣ 具名插槽(多坑专用)

子组件 (Layout.vue)

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

父组件

<template>
  <Layout>
    <!-- 简写:#header -->
    <template #header>
      <h1>网站标题</h1>
    </template>

    <!-- 默认插槽内容 -->
    <p>主要内容区域</p>

    <template #footer>
      <p>© 2024 版权所有</p>
    </template>
  </Layout>
</template>

✅ 特点:

  • 一个组件多个坑

  • 精准投放

  • 常用于布局组件


3️⃣ 作用域插槽(最难但最强 🔥)

子组件 (Table.vue)​ —— 拥有数据

<template>
  <ul>
    <li v-for="item in list" :key="item.id">
      <!-- 把数据传给父组件 -->
      <slot :row="item" :index="index"></slot>
    </li>
  </ul>
</template>

<script>
export default {
  data() {
    return {
      list: [
        { id: 1, name: '张三', age: 20 },
        { id: 2, name: '李四', age: 25 }
      ]
    }
  }
}
</script>

父组件​ —— 决定怎么显示

<template>
  <Table>
    <!-- 接收子组件的数据 -->
    <template #default="slotProps">
      <span>{{ slotProps.row.name }}</span> -
      <span>{{ slotProps.row.age }}岁</span>
    </template>
  </Table>
</template>

✅ 特点:

  • 子组件给数据

  • 父组件定结构

  • UI 组件库的核心技术


三、语法演变史(Vue 2 → Vue 3)

 

版本

写法

状态

Vue 2

slot="header"

❌ 废弃

Vue 2.6+

v-slot:header

✅ 推荐

Vue 3

#header

✅ 最简

简写对照

<!-- 完整写法 -->
<template v-slot:header="data"></template>

<!-- 简写 -->
<template #header="data"></template>

<!-- 无参数简写 -->
<template v-slot="data"></template>
<!-- 等同于 -->
<template #default="data"></template>

四、数据流向对比图

默认插槽 / 具名插槽:
父组件 (HTML结构)  ──────→  子组件 (坑位)

作用域插槽:
子组件 (数据 row)  ──────→  父组件 (使用数据)

五、真实项目应用场景

🎯 场景 1:通用按钮组件

<!-- Button.vue -->
<button class="btn">
  <slot>默认按钮</slot> <!-- 后备内容 -->
</button>

<!-- 使用 -->
<Button>提交</Button>  <!-- 显示:提交 -->
<Button></Button>     <!-- 显示:默认按钮 -->

🎯 场景 2:表格组件(核心应用)

<!-- Table.vue -->
<table>
  <tr v-for="item in data">
    <td>{{ item.id }}</td>
    <td>
      <!-- 把整行数据给父组件 -->
      <slot name="actions" :row="item"></slot>
    </td>
  </tr>
</table>

<!-- 使用 -->
<Table :data="users">
  <template #actions="{ row }">
    <button @click="edit(row)">编辑</button>
    <button @click="delete(row)">删除</button>
  </template>
</Table>

六、常见错误与避坑指南 ⚠️

❌ 错误 1:在父组件访问子组件数据

<!-- ❌ 错误 -->
<Child>
  {{ childData }} <!-- 访问不到! -->
</Child>

✅ 正确做法:用作用域插槽

<Child v-slot="slotProps">
  {{ slotProps.childData }}
</Child>

❌ 错误 2:具名插槽语法混乱

<!-- ❌ 错误 -->
<div slot="header"></div>

<!-- ✅ 正确 -->
<template v-slot:header></template>
<!-- 或 -->
<template #header></template>

七、一句话总结

 

你想做什么?

用什么插槽?

传一段 HTML

默认插槽

传多段 HTML

具名插槽

子组件给父组件数据

作用域插槽

组件有默认内容

后备内容

💡 终极心法:

插槽是“结构”的传递,作用是“数据”的回流。

两者结合 = 组件设计的终极形态。

如果你愿意,我可以下一步帮你用你现在的后台项目,写一个真实可用的 Table 组件,直接用上所有插槽知识 👍

 

这是一个非常容易卡住初学者的点。我们把 <template #default="slotProps">拆碎了来讲,你看完一定能懂。


一句话先给结论 ✅

这行代码的意思是:

👉 父组件在说:

“我要接收子组件通过【默认插槽】发给我的数据,并把数据装进 slotProps这个变量里。”


二、拆开逐个解释(重点!)

1️⃣ #是什么?

# === v-slot:

✅ 只是简写

#default  ===  v-slot:default

2️⃣ default是什么?

default表示 “没有名字的插槽”

 

子组件

父组件

<slot></slot>

#default

✅ 只要是 没写 name 的插槽,都叫 default


3️⃣ ="slotProps"是什么?

👉 这是你定义的变量名

="slotProps"

意思是:

子组件传过来的所有数据

👉 全部放到 slotProps这个对象里

✅ 它不是 Vue 关键字

✅ 你可以随便改名

#default="data"
#default="scope"
#default="banana"

三、用最直白的代码对比(秒懂)

✅ 子组件(发数据的人)

<slot :row="user" :index="i"></slot>

✅ 父组件(收数据的人)

❌ 看不懂的写法

<template #default="slotProps">
  {{ slotProps.row.name }}
</template>

✅ 改写成你熟悉的写法

<template v-slot:default="slotProps">
  {{ slotProps.row.name }}
</template>

✅ 再简化成“解构写法”(最推荐 ✅)

<template #default="{ row, index }">
  {{ row.name }}
</template>

👉 是不是瞬间清楚了?


四、用生活例子秒懂 🧠

📦 快递比喻

 

Vue 概念

生活

子组件

快递员

<slot :row="user">

快递包裹

#default="slotProps"

你签收快递

slotProps

你拿到的快递箱

slotProps.row

箱子里的东西

✅ 你不关心快递员

✅ 你只关心箱子里的东西


五、常见错误(你一定会遇到 ⚠️)

❌ 错误 1:忘了写 template

<Child #default="slotProps"></Child> ❌

✅ 正确:

<Child>
  <template #default="slotProps"></template>
</Child>

❌ 错误 2:以为 slotProps是固定的

#default="slotProps" ❌(名字不能乱猜)

✅ 正确:

#default="data"

六、终极总结(背下来 ✅)

🔹 #= v-slot:

🔹 default= 默认插槽

🔹 slotProps= 随便起的变量名

🔹 子组件发数据,父组件收数据

最推荐写法:

<Child v-slot="{ row }">
  {{ row.name }}
</Child>

如果你愿意,我可以 用你现在做的后台 Table 页面,直接帮你写一个 100% 可用的作用域插槽实战代码,一看就懂 👍

 

image

 

image

 

这两张图片展示了 Vue 作用域插槽(Scoped Slots)​ 中“多个具名插槽同时传递数据”的完整实战写法。

这是 Vue 2.6+ 和 Vue 3 中非常经典且常用的模式。我来帮你把图片中的代码逻辑拆解清楚,特别是你可能存疑的 template结构。

1. 核心逻辑图解

这是一个 “子组件提供数据,父组件决定如何渲染”​ 的过程:

  • 左边(SlotsAttr.vue子组件):

    • 挖了两个坑:name="header"和 name="main"。

    • 关键点:它不光挖坑,还往坑里扔了两个包裹(msg和 job)。

      • header坑里扔了 msg。

      • main坑里扔了 job。

  • 右边(App.vue父组件):

    • 准备了两张网(template),分别去接这两个坑的数据。

    • 重点:注意看第 17 行和第 20 行,两张网是同时存在的,互不干扰。

2. 逐行代码揭秘

第一步:子组件挖坑并扔包裹

<!-- SlotsAttr.vue -->
<slot name="header" :msg="childMessage"></slot>
<!-- 含义:在 header 这个位置,把 childMessage 的值传给外面,外面要用 msg 这个名字接收 -->

(注:图中第二行 name="mian"应该是拼写错误,应为 main)

第二步:父组件用双网接包

这是图二中最核心的部分,展示了如何同时处理两个插槽的数据:

<SlotsAttr>
  <!-- 第一张网:专门接 header 的数据 -->
  <template #header="slotProps">
    <h3>{{ currentTest }}-{{ slotProps.msg }}</h3>
  </template>

  <!-- 第二张网:专门接 main 的数据 -->
  <!-- 注意:即使有两个插槽,每个 template 依然可以独立写 v-slot -->
  <template #main="slotProps">
    {{ slotPros.job }} 
    <!-- ⚠️注意:图中这里写成了 slotPros (多了个s),应该是笔误,正确是 slotProps -->
  </template>
</SlotsAttr>

3. 为什么要把代码写在 <template>里?

你可能会疑惑,为什么不把 #header直接写在 <div>上?

<!-- ❌ 错误的理解写法 -->
<div #header="slotProps">...</div>

原因如下:

  1. #header只能出现在一个元素上:Vue 规定 v-slot(即 #)只能用在 <template>上(或者组件上)。

  2. 保留原始 DOM 结构:如果你写在 <div>上,那个 <div>就会成为实际 DOM 元素渲染出来。但很多时候我们只是想把数据包在 H3 或 P 标签里,不想多嵌套一层 div。<template>是一个虚拟标签,渲染后不会出现在页面结构中。

4. 图中的小彩蛋(Bug 预警 ⚠️)

仔细看图二的第 21 行:

{{ slotPros.job }}

这里的变量名拼错了!

  • 上面定义的是 slotProps(第 17 行)。

  • 下面用的却是 slotPros(多了一个 s)。

如果不修正,页面上是不会显示 jobMessage的。​ 这是一个很好的找错练习。

5. 进阶写法:缩写(Shorthand)

如果你觉得给每个插槽都起一个 slotProps太麻烦(因为它们互不冲突),Vue 允许你在使用时直接解构:

<SlotsAttr>
  <!-- 接 header 的数据,并直接把 msg 解构出来 -->
  <template #header="{ msg }">
    <h3>{{ msg }}</h3>
  </template>

  <!-- 接 main 的数据,并直接把 job 解构出来 -->
  <template #main="{ job }">
    {{ job }}
  </template>
</SlotsAttr>

这样写更清爽,直接拿到需要的变量,不用再通过 .号去取。

总结

这两张图教你的就是:当子组件有多个位置需要透传数据时,父组件就写多个 <template #名字="数据对象">,各管各的,互不打架。

posted on 2026-06-18 11:20  luzhouxiaoshuai  阅读(24)  评论(0)    收藏  举报

导航