Vue3 插槽基础
插槽要解决什么问题?先回想一下 Bootstrap 的卡片(card)。
Bootstrap 是个 CSS 框架,它的 card 实现原理很简单,就是给一个 div 套上样式类。
.card 负责边框、圆角、阴影这些外观,.card-body 只是一块留白的容器,除了这两层样式,Bootstrap 根本不碰里面的内容。
所以你爱放什么放什么,放一段文字、放一个列表、放一张图都行,<div class="card"> 和普通标签没区别,只是外观统一了。
现在想把这个思路搬进 Vue,做一个自己的 Card 组件,外壳固定,内部由每个页面决定。
但 Vue 组件是封装好的,不像 HTML 那样开个标签就能往里塞内容,外壳写好后,里面放什么就成了问题。
更关键的是,里面该放什么,只有用到它的页面(父组件)才知道。
你写 Card 组件时,还没想好它会被用在哪几页,不可能把内容写进去。
内容得由每个页面自己提供,比如首页(父组件)给一段欢迎文字,博客列表(父组件)给文章标题和摘要。
所以内容必须由使用方传给 Card(子组件),Card 只负责把传进来的内容渲染到指定位置。
那怎么把内容传给子组件?如果不用插槽,只有两条路,都很别扭。
一是把内容写死在组件里:
<!-- Card.vue -->
<template>
<div class="card">
<div class="card-tip">这是一张卡片</div>
<p>这里写死了一段文字</p>
</div>
</template>
可是张三要放表单,李四要放另一个组件,只能复制两份几乎一样的 Card,改一处样式要改两处。
二是用 props 传 HTML 字符串:
<Card :html="'<p>这是一段文字</p>'" />
字符串是死的,变量、事件、组件全都绑不上,没法写复杂内容。
插槽(Slot)就是来解决这个矛盾的。
props 是”传数据”,插槽是”传内容 / 传结构”,父组件往子组件的固定位置塞入任意 HTML 或组件。
弹窗、卡片、导航栏、文章列表这类”外壳固定、内部可变”的组件,都靠插槽实现。
默认插槽
子组件里放一个 <slot> 当作”占位口”,父组件往里面放什么,它就在那渲染什么:
<!-- 子组件 Card.vue -->
<template>
<div class="card">
<div class="card-tip">这是一张卡片</div>
<slot /> <!-- 父组件内容塞到这里 -->
</div>
</template>
<!-- 父组件 -->
<Card>
<p>这是卡片里的自定义内容</p>
</Card>
不给插槽提供内容时,可以写默认内容兜底:
<slot>暂无内容</slot>
换句话说:子组件负责”提供位置和默认内容”,父组件负责”决定放什么”。
具名插槽
一个组件往往有多个插槽位(头部、主体、底部),给 <slot> 起 name 名字,父组件用 <template #名字> 精准投递:
<!-- 子组件 LayoutCard.vue -->
<template>
<div class="layout-card">
<header><slot name="header">默认标题</slot></header>
<main><slot /></main>
<footer><slot name="footer" /></footer>
</div>
</template>
<!-- 父组件 -->
<LayoutCard>
<template #header>我被放进 header</template>
<!-- 没写 # 的内容默认进默认插槽(没 name 的那个) -->
<p>我进 main</p>
<template #footer>页脚</template>
</LayoutCard>
#名字是<template v-slot:名字>的简写,具名插槽必须用<template>包裹。<template #header>按名字对应;没名字的<slot />是默认插槽,父组件直接写内容就进默认插槽。
插槽 vs props 怎么选
| 对比 | props | 插槽 |
|---|---|---|
| 传什么 | 数据(字符串、数字、对象…) | 内容(HTML、组件、结构) |
| 谁决定结构 | 子组件里把数据摆成模板 | 父组件直接决定放什么 |
| 典型场景 | 配置项、数量、标题文字 | 卡片内容、弹窗体、导航项 |
只需要文字/数值用 props;需要完整 HTML/自定义组件就用插槽。两者不冲突,常混合使用。
速查卡片
| 场景 | 写法 |
|---|---|
| 预留默认插槽 | 子组件 <slot /> |
| 带默认内容 | 子组件 <slot>兜底文案</slot> |
| 预留具名插槽 | 子组件 <slot name="header" /> |
| 投递具名插槽 | 父组件 <template #header>…</template> |