一、为什么你的组件需要“默认内容”?
想象一下这个场景:你写了个超精美的<FancyButton>组件,结果使用的人忘记写按钮文字,页面上就出现了一个华丽的...空白按钮。这就像给客人准备了镶金边的饭碗却忘了盛饭——尴尬不?
这就是默认插槽的用武之地!它相当于给组件上了道“保险”,当父组件“偷懒”没提供内容时,组件自己能展示预设的默认内容,保证UI不会“走光”。
举个真实案例:
<!-- 你的帅气按钮组件 -->
<template>
<button class="fancy-btn">
<slot></slot>
</button>
</template>
<!-- 别人使用时忘了填内容 -->
<FancyButton /> <!-- 渲染结果:<button class="fancy-btn"></button> -->
看,一个空荡荡的按钮,再 fancy 也白搭!
二、插槽基础:先理解“占位符”的概念
通俗理解:插槽就是组件里的“预留座位”。你在餐厅订位时,服务员会放个“已预订”的牌子——插槽就是这个牌子,告诉父组件:“这儿给你留了位置,随便坐!”
基础语法:
<!-- 子组件 Child.vue -->
<template>
<div class="container">
<h2>我是子组件</h2>
<slot>这里是默认内容,当父组件不传内容时显示</slot>
</div>
</template>
<!-- 父组件使用 -->
<Child>
<p>这里的内容会替换slot标签</p>
</Child>
三、默认插槽的默认内容:最简单的“备胎”方案
语法:直接在<slot>标签内部写内容
<template>
<slot>
<!-- 默认内容 -->
<p>我是默认的备胎内容😭</p>
</slot>
</template>
实战示例1:带默认文案的按钮
<!-- SubmitButton.vue -->
<template>
<button class="submit-btn" @click="$emit('submit')">
<slot>提交</slot>
</button>
</template>
<!-- 使用方式 -->
<SubmitButton /> <!-- 显示“提交” -->
<SubmitButton>保存</SubmitButton> <!-- 显示“保存” -->
实战示例2:用户卡片组件
<!-- UserCard.vue -->
<template>
<div class="user-card">
<div class="avatar">
<slot name="avatar">
<img src="/default-avatar.png" alt="默认头像">
</slot>
</div>
<div class="info">
<h3>
<slot name="name">未知用户</slot>
</h3>
<p>
<slot name="bio">这个人很懒,什么都没有写...</slot>
</p>
</div>
</div>
</template>
四、具名插槽的默认内容:精准设置“备胎”
当你的组件有多个插槽时,就需要具名插槽来精准分配位置。
语法:
<template>
<div>
<slot name="header">
<!-- header的默认内容 -->
<h3>默认标题</h3>
</slot>
<slot>
<!-- 默认插槽的默认内容 -->
<p>主要内容区默认文本</p>
</slot>
<slot name="footer">
<!-- footer的默认内容 -->
<p>© 2023 默认版权信息</p>
</slot>
</div>
</template>
完整示例:文章卡片组件
<!-- ArticleCard.vue -->
<template>
<div class="article-card">
<header class="article-header">
<slot name="title">
<h2>默认文章标题</h2>
</slot>
<slot name="meta">
<div class="meta">发布时间:未知 | 阅读量:0</div>
</slot>
</header>
<main class="article-content">
<slot>
<p>这里还没有内容哦~</p>
</slot>
</main>
<footer class="article-footer">
<slot name="actions">
<button @click="$emit('like')">点赞</button>
<button @click="$emit('share')">分享</button>
</slot>
</footer>
</div>
</template>
五、作用域插槽的默认内容:带数据的“高级备胎”
作用域插槽允许子组件向插槽传递数据,它的默认内容设置稍微复杂点。
语法:
<template>
<!-- 传递user数据给插槽 -->
<slot :user="user">
<!-- 默认内容可以访问相同的数据 -->
<div v-if="user">
默认展示:{{ user.name }}
</div>
<div v-else>
默认内容:用户不存在
</div>
</slot>
</template>
实战示例:用户列表组件
<!-- UserList.vue -->
<template>
<div class="user-list">
<div v-for="user in users" :key="user.id" class="user-item">
<slot :user="user">
<!-- 默认展示方式 -->
<span>{{ user.name }} - {{ user.email }}</span>
</slot>
</div>
</div>
</template>
六、组合API + 插槽默认内容:现代写法示例
现在来看看用Vue 3的组合API怎么写带默认内容的插槽。
<!-- ModernComponent.vue -->
<script setup>
import { ref, computed } from 'vue'
// 组合API的逻辑
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
const increment = () => {
count.value++
}
// 定义插槽的类型(TypeScript用户可取消注释)
// defineSlots<{
// default?: (props: { count: number }) => any
// header?: (props: { title: string }) => any
// }>()
</script>
<template>
<div class="modern-component">
<!-- 具名插槽带默认内容 -->
<slot name="header" :title="'计数器组件'">
<header class="component-header">
<h2>默认标题</h2>
<p>当前计数:{{ count }}</p>
</header>
</slot>
<!-- 默认插槽带作用域数据 -->
<main class="component-main">
<slot :count="count" :double="doubleCount">
<div class="default-content">
<p>这是默认的主要内容区域</p>
<button @click="increment">点我+1</button>
<p>计数:{{ count }},双倍:{{ doubleCount }}</p>
</div>
</slot>
</main>
<!-- 另一个具名插槽 -->
<slot name="footer">
<footer class="component-footer">
<p>默认底部信息</p>
</footer>
</slot>
</div>
</template>
使用示例:
<script setup>
import ModernComponent from './ModernComponent.vue'
</script>
<template>
<!-- 完全使用默认内容 -->
<ModernComponent />
<!-- 部分覆盖默认内容 -->
<ModernComponent>
<template #header="{ title }">
<h1>自定义标题:{{ title }}</h1>
</template>
<template #default="{ count, double }">
<p>自定义内容:计数是{{ count }},双倍是{{ double }}</p>
</template>
</ModernComponent>
</template>
七、高级技巧和最佳实践
- 条件性显示默认内容:
<template>
<slot>
<div v-if="isLoading">加载中...</div>
<div v-else-if="isEmpty">暂无数据</div>
<div v-else>默认内容</div>
</slot>
</template>
- 默认内容中也用组件:
<template>
<slot>
<!-- 默认内容也可以用其他组件 -->
<DefaultAvatar />
<DefaultText />
<ActionButton>默认操作</ActionButton>
</slot>
</template>
- 性能优化提示:
默认内容在子组件中初始化,如果默认内容很复杂,可以考虑用v-if控制渲染:
<template>
<slot>
<HeavyComponent v-if="showDefault" />
</slot>
</template>
八、常见坑和避雷指南
坑1:默认内容中的响应式数据
<!-- 正确 -->
<template>
<slot>
<p>{{ dynamicData }}</p> <!-- 这会响应式更新 -->
</slot>
</template>
<script setup>
import { ref } from 'vue'
const dynamicData = ref('初始值')
</script>
坑2:样式作用域问题
<style scoped>
/* 这些样式只作用于当前组件,包括默认内容 */
.default-content {
color: red;
}
</style>
九、完整实战:构建一个智能表格组件
让我们用一个完整的例子结束今天的学习:
<!-- SmartTable.vue -->
<script setup>
import { computed } from 'vue'
const props = defineProps({
data: {
type: Array,
default: () => []
},
loading: {
type: Boolean,
default: false
}
})
const hasData = computed(() => props.data.length > 0)
</script>
<template>
<div class="smart-table">
<slot name="header">
<div class="table-header">
<h3>数据表格</h3>
</div>
</slot>
<slot :data="data" :loading="loading">
<div v-if="loading" class="table-loading">
加载中...
</div>
<div v-else-if="!hasData" class="table-empty">
<slot name="empty">
<p>暂无数据</p>
<button @click="$emit('refresh')">刷新</button>
</slot>
</div>
<table v-else class="table-content">
<!-- 默认表格渲染逻辑 -->
<thead>
<tr>
<th>ID</th>
<th>名称</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="item in data" :key="item.id">
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>
<button @click="$emit('edit', item)">编辑</button>
</td>
</tr>
</tbody>
</table>
</slot>
<slot name="footer" :total="data.length">
<div class="table-footer">
共 {{ data.length }} 条记录
</div>
</slot>
</div>
</template>
总结
给插槽设置默认内容,就像给你的组件买了份“保险”——希望用不上,但用上的时候真能救急!记住这个核心思想:
好的组件设计要兼顾灵活性和健壮性:既给使用者充分的定制自由,又要确保在“掉链子”时有体面的降级方案。
从现在开始,给你的每个公共组件都加上合理的默认内容吧!这样即使新手同事忘记传内容,你的UI也不会“裸奔”上线。毕竟,在编程世界里,“备胎”有时候比“正主”还重要呢!
彩蛋:试试在你的项目中找一个经常被误用的组件,给它加上贴心的默认内容,观察一下团队里的吐槽是不是变少了?这就是良好组件设计的魅力!
326

被折叠的 条评论
为什么被折叠?



