Vue3 零基础每日笔记(029):插槽 slot 三连——默认、具名、作用域一次讲透

一、前言

props 传数据、emit 传事件,但父组件想把**一段模板(HTML 结构)**塞进子组件呢?比如卡片组件:外壳统一,中间装什么内容每个页面都不一样。这就是插槽 slot:子组件挖坑,父组件填内容。默认、具名、作用域三种,一个比一个强,Element Plus 的表格弹窗全是它的深度应用。

二、默认插槽:子组件挖坑,父组件填内容

先看没有插槽的尴尬:内容写死在子组件里。

vue 复制代码
<!-- 没插槽:MyCard 的中间内容永远是死的 -->
<div class="card">
  <h4>固定标题</h4>
  <p>固定内容------别的页面想换内容?没门</p>
</div>

<slot> 挖个坑:

vue 复制代码
<!-- src/components/BaseCard.vue -->
<template>
  <div class="card">
    <h4>卡片标题(子组件固定)</h4>

    <!-- 坑:父组件塞什么,这里就渲染什么 -->
    <slot>
      <!-- 坑里写的这个是"后备内容":父组件不填时才显示 -->
      <p style="color: #999">(这里暂时没有内容)</p>
    </slot>
  </div>
</template>

<style scoped>
.card { border: 1px solid #eee; padding: 12px; border-radius: 8px; margin-bottom: 12px; }
</style>
vue 复制代码
<!-- App.vue -->
<template>
  <!-- 填坑 1:塞普通内容 -->
  <BaseCard>
    <p>这是父组件塞进来的内容 <b style="color:#42b983">带样式</b></p>
  </BaseCard>

  <!-- 填坑 2:什么都不填 → 显示后备内容 -->
  <BaseCard />

  <!-- 填坑 3:塞组件也行(内容可以是任何模板) -->
  <BaseCard>
    <StarRating :model="4" @change="() => {}" />
  </BaseCard>
</template>

<script setup>
import BaseCard from './components/BaseCard.vue'
import StarRating from './components/StarRating.vue'
</script>

编译本质 :父组件写在标签"夹缝"里的内容,会被塞到子组件 <slot /> 的位置。作用域注意:模板里能用谁的变量,看它定义在哪------插槽内容在父组件编译,只能直接用父组件的数据(想用子组件的看第五节作用域插槽)。

【截图位置:填内容、留空显示后备内容两种卡片的效果】

三、具名插槽:多个坑,按名字填

卡片通常有三段:头、身、脚。一个 slot 不够用,给坑起名字:

vue 复制代码
<!-- src/components/PageCard.vue -->
<template>
  <div class="page-card">
    <header class="head">
      <slot name="header">默认头部</slot>
    </header>

    <main>
      <!-- 不写 name 的坑默认叫 default -->
      <slot />
    </main>

    <footer class="foot">
      <slot name="footer">默认底部</slot>
    </footer>
  </div>
</template>

<style scoped>
.page-card { border: 1px solid #eee; border-radius: 8px; overflow: hidden; }
.head { background: #f8f8f8; padding: 10px 12px; font-weight: bold; }
main { padding: 12px; }
.foot { background: #fafafa; padding: 8px 12px; }
</style>

父组件用 #名字v-slot:名字 的缩写)定向填坑:

vue 复制代码
<template>
  <PageCard>
    <!-- #header = v-slot:header,只填头部坑 -->
    <template #header>
      用户管理 <small>(共 88 人)</small>
    </template>

    <!-- 不包 template 的内容全部进默认坑(主体) -->
    <p>这里是表格区......</p>

    <template #footer>
      <button>保存</button>
      <button>取消</button>
    </template>
  </PageCard>
</template>

规则两条:

  1. #名字 的内容去同名坑,裸内容去 default 坑
  2. 填具名坑必须用 <template #名字> 包裹(v-slot 只能用在 template 或组件标签上)

四、编译作用域:插槽内容只认父组件的数据

强调一遍这个高频误区:

vue 复制代码
<template>
  <!-- ❌ url 是子组件的数据,父组件插槽里用不了 -->
  <!-- <BaseCard><a :href="url">链接</a></BaseCard> -->
</template>

插槽内容编译作用域在父组件 :能不能显示取决于父组件有没有这个变量,跟子组件无关。那如果内容偏偏需要子组件的数据(比如列表项)?------作用域插槽登场。

五、作用域插槽:子组件把数据"递"给插槽内容

场景:列表外壳(ul、边框、空状态)由子组件统一封装,但每行长什么样要父组件自定义,而且渲染需要子组件遍历出来的 item。

子组件挖坑时把数据绑在坑上递出去

vue 复制代码
<!-- src/components/TaskList.vue -->
<template>
  <ul class="task-list">
    <li v-for="(task, index) in tasks" :key="task.id">
      <!-- :item :index 是"递出去的数据",父组件填坑时可接收 -->
      <slot name="item" :item="task" :index="index">
        <!-- 后备内容:父组件不定制时的默认长相 -->
        {{ index + 1 }}. {{ task.text }}
      </slot>
    </li>
    <li v-if="tasks.length === 0" class="empty">暂无任务</li>
  </ul>
</template>

<script setup>
defineProps({
  tasks: { type: Array, default: () => [] }
})
</script>

父组件填坑时用 #item="{ item, index }" 解构接收(就是 017 篇学的解构语法):

vue 复制代码
<template>
  <TaskList :tasks="tasks">
    <template #item="{ item, index }">
      <!-- item / index 是子组件递出来的,随便用 -->
      <span :class="{ done: item.done }">
        {{ index + 1 }}. {{ item.text }}
      </span>
      <button v-if="!item.done" @click="finish(item.id)">完成</button>
    </template>
  </TaskList>
</template>

<script setup>
import { ref } from 'vue'
import TaskList from './components/TaskList.vue'

const tasks = ref([
  { id: 1, text: '学插槽', done: true },
  { id: 2, text: '封装列表组件', done: false }
])

function finish(id) {
  const t = tasks.value.find(t => t.id === id)
  if (t) t.done = true
}
</script>

<style scoped>
.done { text-decoration: line-through; color: #999; }
</style>

这一刻你会突然看懂 Element Plus 的 el-table

vue 复制代码
<el-table :data="list">
  <el-table-column label="状态">
    <template #default="{ row }">{{ row.status }}</template>
  </el-table-column>
</el-table>

#default="{ row }" 就是我们刚写的作用域插槽------el-table 递出 row,你定制每行渲染。组件库设计的底层思想,你已经拿到了。

【截图位置:任务列表自定义行渲染(删除线 + 完成按钮)的运行效果】

六、踩坑记录

  1. 填具名坑忘了包 template<PageCard #header>xxx</PageCard> 无效------v-slot/# 只能写在 <template> 或组件标签上,标准写法 <template #header>
  2. 插槽内容用子组件的变量:编译作用域在父组件,拿不到就 undefined------需要子组件数据请用作用域插槽
  3. 作用域插槽解构名写错 :子组件递的是 :item,父组件解构写 { task } 就是 undefined------名字以子组件绑定时为准
  4. 同一坑重复填 :两个 <template #header> 后者覆盖前者,只生效一个
  5. 动态插槽名知道即可<template #[dynamicName]> 方括号语法存在,低频,遇到了会认就行

七、今日小结

  • 插槽 = 子组件挖坑 <slot>,父组件填模板,坑里可写后备内容
  • 具名插槽 #名字 定向填坑,裸内容进 default
  • 插槽内容编译作用域在父组件
  • 作用域插槽:坑上 :数据 递出去,父组件 #名="{ 解构 }" 接收------el-table 的灵魂

下篇预告

组件通信与组合还剩两员大将:多 Tab 切换时组件反复销毁重建,用户填一半的表单说没就没怎么办?下一篇 030 动态组件与 keep-alive------<component :is> 切换 + 页面缓存保状态。

相关推荐
aixingpan1 小时前
aixingpan.cn API开发文档:api_docs_bichart_natalvstertiary2接口指南
前端·php
铁皮饭盒1 小时前
网页端, 40mb离线模型, 自动抠图, 不用 Python,不用服务器,不用 API Key, 不用显卡
前端·javascript·后端
梦曦i1 小时前
修复 .uvue 页面路径残留问题
前端·vite
nicole bai1 小时前
vue前端全局图标闪一下的问题
前端·vue.js
ly76891 小时前
磁盘 I/O 延迟突增:用 iostat、blktrace 与火焰图定位到具体调用栈
java·linux·前端·数据库·iostat·磁盘 i/o·blktrace
IT_陈寒1 小时前
Redis并发写入踩坑记录:别让超时设置坑了你
前端·人工智能·后端
晨枫阳1 小时前
Client authentication(客户端授权)开关与OAuth2 PKCE的深刻理解
前端·安全
福兮说1 小时前
在浏览器里写一个“够用就好“的 Excel 公式求值器
前端·javascript·excel·编译原理
Bs_MoneyMagnet1 小时前
基于springboot+vue的图书馆预约系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·毕业设计·图书管理系统·计算机毕业设计