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

Vue3里slot default怎么用?从基础到实战一次讲透

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

默认插槽(slot default)到底是什么?

简单说,默认插槽是Vue组件里没给插槽起名字的那类插槽,你可以把组件想象成一个“容器模板”,插槽就是容器里预留的“空位”——父组件往这个空位里塞内容,子组件负责把这些内容渲染到指定位置。

和“具名插槽”对比着看更清楚:具名插槽是给<slot>加了name属性(比如<slot name="header"></slot>),父组件得用<template #header>这种方式对应;而默认插槽没name,父组件直接在子组件标签里写内容会自动跑到子组件的<slot></slot>位置里。

举个生活例子:你点外卖时,商家给的餐盒是“子组件”,餐盒上有个“默认放饭菜的格子”(对应default slot),你把点的菜(父组件内容)放进这个格子,商家拿到后直接把格子里的菜放进餐盒——不用给这个格子贴标签,因为它是“默认放主菜”的位置。

怎么在父组件和子组件之间用default slot传内容?

这得分成“子组件定义插槽”和“父组件传递内容”两步来做:

子组件里定义default slot

子组件的模板中,只要写一个<slot></slot>标签,就相当于“挖了个默认的坑”,比如做个卡片组件MyCard

<template>
  <div class="card">
    <div class="card-header">固定的头部</div>
    <slot></slot> <!-- 这里是默认插槽的位置 -->
    <div class="card-footer">固定的底部</div>
  </div>
</template>

这个<slot></slot>就是父组件内容要插入的地方,如果子组件没写<slot>,父组件往子组件标签里塞的内容根本没地方放,最终页面上不会显示。

父组件往default slot传内容

父组件用子组件时,直接在子组件标签内部写内容,这些内容会自动“填”到子组件的<slot></slot>位置,比如父组件用MyCard

<template>
  <MyCard>
    <!-- 这里所有内容都会进MyCard的default slot -->
    <h2>这是卡片标题</h2>
    <p>这是卡片正文,能自定义任何结构</p>
    <button>卡片里的按钮</button>
  </MyCard>
</template>

渲染后,MyCard里的<slot></slot>会被替换成父组件写的<h2><p><button>,最终页面上卡片的头部、底部是固定的,中间内容是父组件传的自定义内容。

default slot和具名插槽核心区别是啥?

最直观的区别是有没有“名字”,但实际用的时候差异更明显:

对比项 默认插槽(default slot) 具名插槽(named slot)
插槽定义 子组件写<slot></slot>(无name 子组件写<slot name="xxx"></slot>
适用场景 组件只有一个“主要自定义区域” 组件有多个“分区域自定义”需求

举个实际例子:做一个页面布局组件PageFrame,需要头部、主体、底部三个可自定义区域,如果主体是“默认区域”,头部和底部是“具名区域”,代码会这样写:

子组件PageFrame

<template>
  <div class="page">
    <header>
      <slot name="header"></slot> <!-- 具名插槽header -->
    </header>
    <main>
      <slot></slot> <!-- 默认插槽,对应主体 -->
    </main>
    <footer>
      <slot name="footer"></slot> <!-- 具名插槽footer -->
    </footer>
  </div>
</template>

父组件用PageFrame时:

<PageFrame>
  <!-- 具名插槽header的内容,必须用<template #header>包起来 -->
  <template #header>
    <h1>网站标题</h1>
  </template>
  <!-- 默认插槽的内容,直接写,不用包template -->
  <p>这是页面主体内容,自动进main里的default slot</p>
  <!-- 具名插槽footer的内容,用<template #footer>包 -->
  <template #footer>
    <p>©2024 版权信息</p>
  </template>
</PageFrame>

能看出来:默认插槽不用写<template #xxx>更“直接”;具名插槽必须用<template>配合name,才能精准对应子组件的命名插槽。

作用域插槽在default里咋玩?

作用域插槽是Vue里“子组件给父组件传数据”的关键玩法——子组件可以把自己的数据,通过<slot>传递给父组件,让父组件基于子组件数据自定义渲染内容,而default slot也能玩这套逻辑~

步骤分两步:子组件传数据给slot父组件用v-slot接收数据

子组件:给default slot绑数据

子组件在<slot>上通过v-bind(或简写)传递数据,比如做个用户信息组件UserProfile,要把用户对象传给父组件:

<template>
  <div class="user-box">
    <!-- 给default slot传user数据 -->
    <slot :user="user"></slot>
  </div>
</template>
<script setup>
import { ref } from 'vue'
const user = ref({ name: '阿花', age: 25, job: '设计师' })
</script>

父组件:用v-slot接收数据

父组件在子组件标签上用v-slot指令(或简写),捕获子组件传的内容,比如父组件要自定义用户信息的展示:

<template>
  <UserProfile v-slot="scope">
    <!-- scope是个对象,包含子组件传的user -->
    <p>姓名:{{ scope.user.name }}</p>
    <p>年龄:{{ scope.user.age }}</p>
    <p>职业:{{ scope.user.job }}</p>
  </UserProfile>
</template>

如果觉得scope写起来麻烦,还能结构赋值

<UserProfile v-slot="{ user }">
  <p>姓名:{{ user.name }}</p>
  <p>年龄:{{ user.age }}</p>
</UserProfile>

这样父组件既能拿到子组件的user数据,又能自由决定怎么渲染这些数据——而这一切,都是通过default slot完成的~

实战中default slot能解决哪些真实场景?

默认插槽的灵活性,让它在“组件需要一个主要自定义区域”的场景里特别好用,分享三个常见场景:

场景1:通用布局组件

很多项目需要统一的页面结构(比如头部导航、底部版权、中间主体),用default slot做“中间主体”的自定义区域,能让代码更简洁:

子组件BasicLayout

<template>
  <div class="layout">
    <nav class="nav">
      <slot name="nav"></slot> <!-- 具名插槽:导航 -->
    </nav>
    <section class="main">
      <slot></slot> <!-- 默认插槽:主体内容 -->
    </section>
    <footer class="footer">
      <slot name="footer"></slot> <!-- 具名插槽:页脚 -->
    </footer>
  </div>
</template>

父组件用BasicLayout时,主体内容直接写,不用给slot起名字:

<BasicLayout>
  <template #nav>
    <a href="/">首页</a>
    <a href="/about">lt;/a>
  </template>
  <!-- 主体内容直接写,进default slot -->
  <article>
    <h2>文章标题</h2>
    <p>文章正文...</p>
  </article>
  <template #footer>
    <p>联系我们:xxx@xxx.com</p>
  </template>
</BasicLayout>

场景2:可自定义的弹窗组件

弹窗(Dialog)组件通常需要“弹窗内容”和“按钮操作区”两个自定义区域,把“弹窗内容”作为default slot,“按钮区”作为具名插槽,用起来更顺手:

子组件Dialog

<template>
  <div class="dialog-mask">
    <div class="dialog-box">
      <div class="dialog-content">
        <slot></slot> <!-- 默认插槽:弹窗内容 -->
      </div>
      <div class="dialog-footer">
        <slot name="footer"></slot> <!-- 具名插槽:按钮区 -->
      </div>
    </div>
  </div>
</template>

父组件调用Dialog时,弹窗内容直接写,按钮区用具名插槽:

<Dialog>
  <!-- 弹窗内容进default slot -->
  <p>确认删除这条数据吗?</p>
  <p>删除后无法恢复哦~</p>
  <!-- 按钮区进具名插槽footer -->
  <template #footer>
    <button @click="cancel">取消</button>
    <button @click="confirm">确认</button>
  </template>
</Dialog>

场景3:动态列表渲染

做列表组件时,往往需要“父组件自定义列表项的UI”(比如有的列表显示文字,有的显示卡片),用default slot结合作用域插槽,能完美实现:

子组件MyList

<template>
  <ul class="list">
    <li v-for="item in list" :key="item.id">
      <!-- 给每个列表项的default slot传item数据 -->
      <slot :item="item"></slot>
    </li>
  </ul>
</template>
<script setup>
const props = defineProps(['list'])
</script>

父组件用MyList时,自定义每个列表项的显示:

<template>
  <MyList :list="taskList">
    <!-- 用v-slot接收每个item -->
    <template v-slot="{ item }">
      <div class="task-card">
        <h3>{{ item.title }}</h3>
        <p>{{ item.desc }}</p>
        <span>{{ item.status }}</span>
      </div>
    </template>
  </MyList>
</template>
<script setup>
import { ref } from 'vue'
const taskList = ref([
  { id: 1, title: '任务1', desc: '...', status: '进行中' },
  { id: 2, title: '任务2', desc: '...', status: '已完成' }
])
</script>

这里子组件循环渲染列表项,每个项的内容由父组件通过default slot自定义——既保留了列表的结构逻辑(子组件负责循环),又让UI完全可定制(父组件负责渲染)。

default slot的进阶玩法有哪些?

除了基础用法,默认插槽还有些“隐藏技巧”能提升开发效率:

技巧1:Fallback内容(兜底内容)

子组件的<slot>里可以写“默认内容”——如果父组件没传内容,就显示这个默认内容;父组件传了内容,就替换掉默认内容。

比如做个评论组件CommentBox,没人评论时显示提示:

<template>
  <div class="comment-list">
    <slot>
      <p>还没有人发表评论~</p> <!-- fallback内容 -->
    </slot>
  </div>
</template>

时,页面显示“还没有人发表评论~”;如果父组件传了评论列表:

<CommentBox>
  <div v-for="comment in comments" :key="comment.id">
    <p>{{ comment.author }}:{{ comment.content }}</p>
  </div>
</CommentBox>

这时子组件的fallback内容会被替换,显示真实评论。

技巧2:在slot里用v-if/v-for

不管是父组件传的内容,还是子组件slot里的fallback内容,都能正常用Vue的指令。

比如父组件给default slot传一个带v-for的列表:

<MyComponent>
  <ul>
    <li v-for="item in menuList" :key="item.id">{{ item.name }}</li>
  </ul>
</MyComponent>

子组件的slot里也能包条件渲染:

<template>
  <div class="box">
    <slot>
      <!-- fallback内容里用v-if -->
      <p v-if="showTip">请先添加内容</p>
    </slot>
  </div>
</template>
<script setup>
const showTip = ref(true)
</script>

技巧3:嵌套插槽(谨慎使用)

理论上,default slot里还能嵌套其他组件的插槽,但这种情况容易让代码复杂度飙升,除非是极复杂的组件嵌套,否则不建议。

子组件A里的default slot,又包含子组件B的default slot:

<template>
  <div class="a">
    <slot>
      <!-- 子组件A的default slot里嵌套子组件B -->
      <B>
        <slot></slot> <!-- 子组件B的default slot -->
      </B>
    </slot>
  </div>
</template>

这种写法会让插槽的层级和数据传递变得很绕,维护成本高,如果不是必须,优先用“单一插槽职责”设计组件。

遇到default slot不生效咋排查?

开发中遇到“父组件传的内容没显示”,可以从这几个方向排查:

原因1:子组件没写<slot>

子组件的模板里如果没有<slot></slot>,父组件往子组件标签里塞的内容没有挂载点,自然不会显示,打开子组件文件,检查是否有<slot>标签。

原因2:作用域插槽数据没接对

如果用了作用域插槽(子组件传数据给父组件),要检查:

  • 子组件<slot>上的v-bind变量名是否正确(比如子传user,父组件v-slot里要拿user);
  • 父组件v-slot的接收语法是否正确(比如v-slot="scope"scope里有没有对应的字段)。

举个错误例子:子组件传的是user,父组件写成v-slot="{ username }",这时候usernameundefined就会不显示。

原因3:插槽被条件渲染覆盖

子组件里的<slot>如果被v-if包着,且条件不满足,slot就不会渲染,父组件内容也跟着消失。

<template>
  <div>
    <slot v-if="isShow"></slot> <!-- isShow为false时,slot不渲染 -->
  </div>
</template>

检查子组件slot的渲染条件是否正确。

原因4:多个default slot导致冲突

子组件里如果写了多个<slot></slot>(没有name的那种),父组件传的内容会被每个slot重复渲染,或者因为插槽合并逻辑导致显示异常,Vue官方建议:一个子组件里只写一个default slot,避免混淆。

default slot是Vue3插槽体系里“最灵活、最基础”的存在——它让父组件能以最简洁的方式给子组件传内容,同时结合作用域插槽、fallback内容等特性,能覆盖从简单到复杂的组件通信场景,掌握它的关键是理解“匿名插槽的定位”+“父子组件的配合逻辑”+“实战场景的灵活运用”,多写几个例子,自然就熟了~

版权声明

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

热门