Vue基础教程(123)组件和组合API之插槽中的默认内容:Vue插槽默认内容:给你的组件上个“安全套”,防止UI“走光”!

一、为什么你的组件需要“默认内容”?

想象一下这个场景:你写了个超精美的<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>

七、高级技巧和最佳实践

  1. 条件性显示默认内容
<template>
  <slot>
    <div v-if="isLoading">加载中...</div>
    <div v-else-if="isEmpty">暂无数据</div>
    <div v-else>默认内容</div>
  </slot>
</template>
  1. 默认内容中也用组件
<template>
  <slot>
    <!-- 默认内容也可以用其他组件 -->
    <DefaultAvatar />
    <DefaultText />
    <ActionButton>默认操作</ActionButton>
  </slot>
</template>
  1. 性能优化提示
    默认内容在子组件中初始化,如果默认内容很复杂,可以考虑用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也不会“裸奔”上线。毕竟,在编程世界里,“备胎”有时候比“正主”还重要呢!


彩蛋:试试在你的项目中找一个经常被误用的组件,给它加上贴心的默认内容,观察一下团队里的吐槽是不是变少了?这就是良好组件设计的魅力!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

值引力

持续创作,多谢支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值