Code前端首页关于Code前端联系我们

Vue3 的 slot 怎么用?从基础到实战全解析

terry 2小时前 阅读数 3 #SEO
文章标签 Vue3 slot

新手学 Vue3 时,slot 常常让人摸不着头脑:到底啥是 slot?具名插槽、作用域插槽有啥区别?实际项目怎么用?今天用问答形式把 slot 讲透,从基础到实战一次性搞懂~

Vue3 里的 slot 是干啥的?

简单说,slot 是 Vue 里的“内容分发机制”——让父组件能控制子组件里的部分 UI 结构,同时子组件保留逻辑和样式的控制权。

举个实际场景:做一个弹窗组件 <Dialog>,弹窗的“标题”“底部按钮”想让父组件自定义,而“弹窗遮罩、关闭逻辑”由子组件自己实现,这时候用 slot 就很合适:子组件里留好 <slot> 占位,父组件往对应位置塞内容就行。

最基础的“匿名插槽”怎么写?

匿名插槽是最简单的用法,子组件里只放一个 <slot>,父组件往子组件标签里塞内容,就会替换这个 <slot>

子组件(MyButton.vue):

<template>
  <button class="btn">
    <!-- 这里是插槽,父组件内容会替换这里 -->
    <slot>默认按钮文字</slot> 
  </button>
</template>
<style scoped>
.btn { padding: 8px 16px; }
</style>

父组件中使用:

<template>
  <!-- 情况1:父组件传内容,替换默认值 -->
  <MyButton>提交</MyButton>  
  <!-- 情况2:父组件没传内容,显示 slot 里的默认值“默认按钮文字” -->
  <MyButton></MyButton>  
</template>

关键点:匿名插槽只有一个,父组件没写内容时,子组件 <slot> 中间的“默认按钮文字”会显示(这叫 Fallback 内容)。

多个插槽咋区分?用“具名插槽”!

如果子组件需要多个可自定义的区域(比如页面布局有 header、main、footer),就得用具名插槽——给每个 <slot>name 属性区分。

子组件(Layout.vue):

<template>
  <div class="layout">
    <!-- 头部插槽,name 是 header -->
    <slot name="header"></slot>  
    <!-- 主体插槽,name 是 main -->
    <slot name="main"></slot>  
    <!-- 底部插槽,name 是 footer -->
    <slot name="footer"></slot>  
  </div>
</template>

父组件中使用:

父组件要用 <template> 配合 v-slot:(或缩写 )指定插槽名,把内容“精准投递”到对应位置。

<template>
  <Layout>
    <!-- 投递到 header 插槽 -->
    <template #header>  
      <h1>页面标题</h1>
    </template>
    <!-- 投递到 main 插槽 -->
    <template #main>  
      <p>这里是正文内容...</p>
    </template>
    <!-- 投递到 footer 插槽 -->
    <template #footer>  
      <p>©2024 版权信息</p>
    </template>
  </Layout>
</template>

小细节:v-slot: 可以缩写成 ,但必须配合 <template> 使用(不能直接写在普通 DOM 元素上,除非是组件标签,但日常用 <template> 更规范)。

子组件的数据,父组件插槽想用?用“作用域插槽”!

场景:子组件里有循环数据(todo 列表),但每个 todo 项的 UI 想让父组件自定义,这时候子组件需要把内部数据传给父组件的插槽,这就是作用域插槽

子组件(TodoList.vue):

子组件循环 todos 数组,把每个 todo 数据通过 todo="todo" 传给插槽。

<template>
  <ul class="todo-list">
    <li v-for="todo in todos" :key="todo.id">
      <!-- 插槽绑定 todo 数据 -->
      <slot :todo="todo"></slot>  
    </li>
  </ul>
</template>
<script setup>
import { ref } from 'vue'
const todos = ref([
  { id: 1, title: '学习 Vue3', done: false },
  { id: 2, title: '写代码', done: true }
])
</script>

父组件中使用:

父组件用 #default="slotProps" 接收子组件传的内容,slotProps 是个对象,里面包含子组件绑定的 todo 数据,还能解构赋值简化写法~

<template>
  <TodoList>
    <!-- 方式1:接收整个 slotProps 对象 -->
    <template #default="slotProps">  
      <span v-if="slotProps.todo.done">✔️</span>
      {{ slotProps.todo.title }}
    </template>
    <!-- 方式2:解构赋值(更简洁) -->
    <template #default="{ todo }">  
      <span v-if="todo.done">✔️</span>
      {{ todo.title }}
    </template>
  </TodoList>
</template>

核心逻辑:子组件把数据“绑”在 <slot> 上(todo="todo"),父组件通过 #插槽名="变量" 接收,实现子传父数据 + 父组件自定义 UI的协作。

插槽名能动态变?试试“动态插槽名”!

场景:父组件想根据用户操作/配置,动态选择往子组件的哪个插槽里塞内容,Vue3 支持用方括号语法实现动态插槽名。

父组件示例:

<template>
  <div>
    <!-- 动态变量 slotName,比如可能是 'header' 或 'footer' -->
    <template #[slotName]>  
      {{ 动态插槽的内容 }}
    </template>
    <!-- 也可以结合 v-bind 动态绑定 -->
    <template v-slot:[dynamicSlotName]>  
      {{ 内容 }}
    </template>
  </div>
</template>
<script setup>
import { ref } from 'vue'
const slotName = ref('header') // 动态切换插槽名
const dynamicSlotName = ref('footer')
</script>

子组件只需要对应提供 nameheaderfooter 等的具名插槽即可,父组件动态选择要“投递”的目标。

插槽的“Fallback 内容”有啥用?

Fallback 备胎内容”——当父组件没给插槽传内容时,子组件 <slot> 中间的内容会显示。

比如子组件想给按钮默认文字,但允许父组件自定义:

<template>
  <button>
    <slot>默认按钮</slot> <!-- 父组件没传内容时,显示“默认按钮” -->
  </button>
</template>

作用域插槽也能结合 Fallback!比如子组件循环列表,父组件没传插槽内容时,显示默认的列表项样式:

<template>
  <li v-for="item in list" :key="item.id">
    <slot :item="item">
      <!-- Fallback 内容:默认显示 item.name -->
      {{ item.name }}
    </slot>
  </li>
</template>

Vue3 和 Vue2 的 slot 有啥区别?

很多从 Vue2 转 Vue3 的同学会疑惑语法变化,3 个核心区别:

  • 语法更统一:Vue2 里父组件用 <template slot="name">,子组件用 <slot name="name">;Vue3 统一用 v-slot: 或 ,且必须写在 <template> 上。
  • 作用域插槽更高效:Vue3 对作用域插槽的编译做了优化,性能更好,且语法更简洁(不用再写 slot-scope)。
  • 组合式 API 支持:Vue3 可以在 setup 里用 useSlots() 操作插槽(比如判断某个插槽是否存在),Vue2 没有这个 API。

插槽和组件通信有啥关系?

Vue 里组件通信方式很多(props、events、provide/inject),插槽是“内容级通信”——父组件给子组件传“带逻辑的模板”,子组件给父组件传“数据”(作用域插槽)。

举个经典场景:评论列表组件

  • 子组件 <CommentList> 负责拉取评论数据、处理加载状态。
  • 父组件想自定义每条评论的 UI(比如加头像、点赞按钮)。
  • 这时用作用域插槽:子组件把评论数据传给插槽,父组件接收后渲染自定义结构,完美解耦“数据逻辑”和“UI 展示”。

实战中插槽常见问题咋解决?

新手用插槽容易踩坑,分享 3 个高频问题和解法:

问题1:作用域插槽接收不到数据?

→ 检查两点:

  • 子组件是否用 绑定数据(<slot :todo="todo">,少了 就变成字符串传值了)。
  • 父组件是否正确接收(#default="{ todo }",别写错成 slotProps.todo 但没解构)。

问题2:具名插槽不生效?

→ 检查 name 匹配度:子组件 <slot name="header"> 和父组件 <template #header> 必须完全一致,大小写也要注意!

问题3:默认插槽和具名插槽混用混乱?

→ 父组件中,没写 <template #name> 的内容,会默认进入“匿名插槽”,如果子组件同时有匿名和具名插槽,要明确区分内容归属,避免结构混淆。

插槽在 UI 组件库中咋用?

主流 UI 库(Element Plus、Ant Design Vue)大量用插槽增强扩展性,举两个例子:

  • 表格组件用作用域插槽自定义,<el-table-column>#default="scope"scope 里包含行数据、索引等。
  • 弹窗组件、底部按钮用具名插槽,<el-dialog>#title 自定义标题,#footer 自定义底部按钮。

学会插槽,你也能写出像 UI 库一样灵活的组件~

组合式 API 咋操作插槽?

Vue3 的 setup 中,用 useSlots() 可以获取当前组件的所有插槽,返回一个对象(键是插槽名,值是插槽内容的 VNode 数组)。

子组件示例(判断插槽是否存在):

<template>
  <div>
    <slot name="header"></slot>
    <slot></slot>
  </div>
</template>
<script setup>
import { useSlots } from 'vue'
const slots = useSlots()
// 检查是否有 header 插槽
if (slots.header) {
  console.log('父组件提供了 header 插槽')
}
// 检查默认插槽是否有内容
if (slots.default) {
  console.log('父组件给默认插槽传了内容')
}
</script>

这个 API 能让你在逻辑层动态处理插槽(比如插槽存在才渲染某部分),灵活度拉满~

slot 是 Vue 组件解耦的神器!

从基础匿名插槽,到具名、作用域、动态插槽,核心都是让父组件和子组件在“内容和逻辑”上各司其职:子组件管逻辑/样式,父组件管自定义结构,配合起来实现高度可复用的组件。

实际项目中,只要涉及“组件部分结构需要外部自定义”的场景,第一时间想到 slot 就对了~ 现在再回头看项目里的弹窗、表格、列表组件,是不是突然明白它们的插槽逻辑了?
帮你理清了 slot 用法,不妨动手写个带作用域插槽的列表组件试试,实践出真知~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门