09-Slot 插槽与动态组件

发布时间:2026/7/1 13:02:45

09-Slot 插槽与动态组件 Slot 插槽与动态组件掌握 Vue3 插槽系统与动态组件实现高度可复用、灵活的组件化架构。一、前言插槽Slot是 Vue 组件化开发的核心特性之一它让父组件可以向子组件的指定位置插入内容实现组件的内容分发。动态组件则允许我们在同一个挂载点切换不同的组件配合KeepAlive还能缓存组件状态。本章将系统讲解 Vue3 中插槽的三种类型、动态组件的使用方法以及 Vue3 在插槽实现上的重要变化。二、插槽基础2.1 默认插槽默认插槽是最简单的插槽形式子组件通过slot标签预留内容位置。子组件BaseCard.vuetemplate div classcard div classcard-header h3{{ title }}/h3 /div div classcard-body !-- 默认插槽出口 -- slot默认内容当父组件未提供内容时显示/slot /div /div /template script setup defineProps({ title: { type: String, default: 卡片标题 } }) /script style scoped .card { border: 1px solid #e0e0e0; border-radius: 8px; padding: 16px; margin-bottom: 16px; } .card-header { border-bottom: 1px solid #eee; padding-bottom: 12px; margin-bottom: 12px; } /style父组件使用template BaseCard title用户信息 !-- 插入到默认插槽 -- p用户名张三/p p邮箱zhangsanexample.com/p /BaseCard !-- 不提供内容显示默认内容 -- BaseCard title空卡片 / /template script setup import BaseCard from ./BaseCard.vue /script2.2 具名插槽当组件需要在多个位置分发内容时可以使用具名插槽。子组件LayoutPage.vuetemplate div classlayout header classlayout-header slot nameheader默认页头/slot /header aside classlayout-sidebar slot namesidebar默认侧边栏/slot /aside main classlayout-main slot默认主内容区/slot /main footer classlayout-footer slot namefooter默认页脚/slot /footer /div /template script setup // 布局组件提供多个具名插槽 /script style scoped .layout { display: grid; grid-template-areas: header header sidebar main footer footer; grid-template-columns: 200px 1fr; grid-template-rows: auto 1fr auto; min-height: 100vh; gap: 16px; } .layout-header { grid-area: header; background: #f5f5f5; padding: 16px; } .layout-sidebar { grid-area: sidebar; background: #fafafa; padding: 16px; } .layout-main { grid-area: main; padding: 16px; } .layout-footer { grid-area: footer; background: #f5f5f5; padding: 16px; } /style父组件使用三种语法template LayoutPage !-- 方式1v-slot 指令 -- template v-slot:header h1网站管理系统/h1 /template !-- 方式2简写 # -- template #sidebar nav a href#首页/a a href#用户管理/a a href#系统设置/a /nav /template !-- 默认插槽 -- template #default h2欢迎回来管理员/h2 p今日访问数据概览.../p /template !-- 页脚 -- template #footer p© 2026 版权所有/p /template /LayoutPage /template script setup import LayoutPage from ./LayoutPage.vue /script注意v-slot必须用在template上默认插槽内容可直接放在组件标签内。2.3 作用域插槽作用域插槽允许子组件向父组件传递数据实现插槽内容由父组件决定但数据由子组件提供。子组件UserList.vuetemplate ul classuser-list li v-foruser in users :keyuser.id classuser-item !-- 将用户数据通过插槽传递给父组件 -- slot :useruser :indexuser.id !-- 默认渲染 -- {{ user.name }} - {{ user.email }} /slot /li /ul /template script setup defineProps({ users: { type: Array, required: true } }) /script父组件使用template UserList :usersuserList !-- 解构插槽 props -- template #default{ user, index } div classcustom-user span classindex{{ index }}./span img :srcuser.avatar :altuser.name classavatar / div classinfo strong{{ user.name }}/strong span :class{ vip: user.isVip }{{ user.email }}/span /div /div /template /UserList /template script setup import { ref } from vue import UserList from ./UserList.vue const userList ref([ { id: 1, name: 张三, email: zsexample.com, avatar: /a1.jpg, isVip: true }, { id: 2, name: 李四, email: lsexample.com, avatar: /a2.jpg, isVip: false }, { id: 3, name: 王五, email: wwexample.com, avatar: /a3.jpg, isVip: true } ]) /script style scoped .custom-user { display: flex; align-items: center; gap: 12px; padding: 8px; } .avatar { width: 40px; height: 40px; border-radius: 50%; } .vip { color: #ff6b6b; font-weight: bold; } /style三、高级插槽技巧3.1 条件插槽Vue3 提供了useSlots()来检测父组件是否提供了某个插槽的内容。template div classpanel !-- 只有当父组件提供了 header 插槽时才渲染 -- div v-if$slots.header classpanel-header slot nameheader / /div div classpanel-body slot / /div !-- 条件渲染 footer -- div v-if$slots.footer classpanel-footer slot namefooter / /div /div /template script setup import { useSlots } from vue const slots useSlots() // 可以在脚本中判断插槽是否存在 console.log(是否有header插槽, !!slots.header) console.log(是否有默认插槽, !!slots.default) /script3.2 动态插槽名插槽名可以是动态的这在需要根据状态切换插槽内容时非常有用。template BaseLayout !-- 动态插槽名 -- template #[dynamicSlotName] p这是动态插槽内容/p /template /BaseLayout /template script setup import { ref } from vue import BaseLayout from ./BaseLayout.vue // 根据当前路由或状态决定使用哪个插槽 const dynamicSlotName ref(header) // 可以动态切换 const switchSlot () { dynamicSlotName.value dynamicSlotName.value header ? footer : header } /script四、动态组件4.1 基本用法component内置组件配合:is属性可以动态切换组件。template div classtab-container !-- 切换按钮 -- div classtab-bar button v-fortab in tabs :keytab.name :class{ active: currentTab tab.name } clickcurrentTab tab.name {{ tab.label }} /button /div !-- 动态组件 -- component :iscurrentComponent classtab-content / /div /template script setup import { ref, computed } from vue import UserProfile from ./UserProfile.vue import UserSettings from ./UserSettings.vue import UserOrders from ./UserOrders.vue const currentTab ref(profile) const tabs [ { name: profile, label: 个人资料, component: UserProfile }, { name: settings, label: 账号设置, component: UserSettings }, { name: orders, label: 订单记录, component: UserOrders } ] const currentComponent computed(() { const tab tabs.find(t t.name currentTab.value) return tab ? tab.component : null }) /script:is的值可以是组件选项对象、组件名字符串已注册、HTML 标签字符串。4.2 KeepAlive 缓存组件切换动态组件时组件会被销毁和重建。使用KeepAlive可以缓存组件实例保留组件状态。template div classtab-container div classtab-bar button v-fortab in tabs :keytab :class{ active: currentTab tab } clickcurrentTab tab {{ tab }} /button /div !-- KeepAlive 包裹动态组件 -- KeepAlive component :iscurrentTabComponent / /KeepAlive /div /template script setup import { ref, computed } from vue import HomeView from ./HomeView.vue import ArticleView from ./ArticleView.vue import AboutView from ./AboutView.vue const currentTab ref(Home) const tabs [Home, Article, About] const currentTabComponent computed(() { const map { Home: HomeView, Article: ArticleView, About: AboutView } return map[currentTab.value] }) /script4.3 KeepAlive 配置template KeepAlive :include[HomeView, UserProfile] !-- 仅缓存指定组件组件名 -- :exclude[TempView] !-- 不缓存指定组件 -- :max10 !-- 最大缓存实例数LRU 淘汰 -- component :iscurrentView / /KeepAlive /templateKeepAlive 生命周期钩子script setup import { onActivated, onDeactivated } from vue // 组件被激活时调用从缓存恢复 onActivated(() { console.log(组件被激活恢复数据...) // 可以在这里刷新数据 }) // 组件被停用时调用进入缓存 onDeactivated(() { console.log(组件被缓存清理定时器...) // 可以在这里暂停轮询等操作 }) /scriptonActivated在组件首次挂载时也会调用onDeactivated在组件卸载时也会调用。五、Vue3 插槽的变化5.1 内部实现变化Vue3 中所有插槽统一为函数形式带来了更好的性能Vue2作用域插槽是函数普通插槽不是Vue3所有插槽都是函数统一处理优化了编译和运行时性能// Vue3 中$slots 的结构console.log(this.$slots)// {// default: () [...], // 函数// header: () [...], // 函数// footer: (props) [...] // 作用域插槽函数接收参数// }5.2 Vue2 vs Vue3 插槽语法对比特性Vue2 语法Vue3 语法说明默认插槽slot/slotslot/slot无变化具名插槽slot nameheader/slotslot nameheader/slot无变化作用域插槽子组件slot :useruser/slotslot :useruser/slot无变化具名插槽父组件v-slot:header/#headerv-slot:header/#header无变化作用域插槽父组件v-slotslotPropsv-slotslotProps无变化解构插槽 Props支持支持v-slot{ user }动态插槽名v-slot:[dynamicSlot]#[dynamicSlot]Vue3 更简洁插槽检测this.$slots.headeruseSlots()Vue3 推荐组合式 API插槽内容访问this.$scopedSlotsuseSlots()Vue3 统一为$slots插槽默认内容支持支持slot默认内容/slot5.3 废弃的语法以下 Vue2 语法在 Vue3 中已被移除!-- Vue2 已废弃Vue3 不支持 --templateslotheader!-- 使用 v-slot:header --templateslot-scopeprops!-- 使用 v-slotprops --templatescopeprops!-- 使用 v-slotprops --六、综合示例可复用表格组件下面是一个结合插槽与动态组件的综合示例——可配置表格组件。!-- DataTable.vue -- template table classdata-table thead tr th v-forcol in columns :keycol.key {{ col.title }} /th th v-if$slots.action操作/th /tr /thead tbody tr v-for(row, rowIndex) in data :keyrowIndex td v-forcol in columns :keycol.key !-- 列插槽允许自定义列渲染 -- slot :namecol-${col.key} :rowrow :valuerow[col.key] :indexrowIndex {{ row[col.key] }} /slot /td td v-if$slots.action slot nameaction :rowrow :indexrowIndex / /td /tr /tbody /table /template script setup defineProps({ columns: { type: Array, required: true // [{ key: name, title: 姓名 }, ...] }, data: { type: Array, default: () [] } }) /script style scoped .data-table { width: 100%; border-collapse: collapse; } .data-table th, .data-table td { border: 1px solid #ddd; padding: 12px; text-align: left; } .data-table th { background: #f5f5f5; } /style使用template DataTable :columnscolumns :datatableData !-- 自定义状态列 -- template #col-status{ value } span :class[tag, value active ? tag-success : tag-gray] {{ value active ? 启用 : 禁用 }} /span /template !-- 自定义操作列 -- template #action{ row } button clickedit(row)编辑/button button clickremove(row)删除/button /template /DataTable /template script setup import { ref } from vue import DataTable from ./DataTable.vue const columns [ { key: name, title: 姓名 }, { key: email, title: 邮箱 }, { key: status, title: 状态 } ] const tableData ref([ { name: 张三, email: zsexample.com, status: active }, { name: 李四, email: lsexample.com, status: inactive } ]) const edit (row) console.log(编辑, row) const remove (row) console.log(删除, row) /script style .tag { padding: 2px 8px; border-radius: 4px; font-size: 12px; } .tag-success { background: #e6f7e6; color: #52c41a; } .tag-gray { background: #f5f5f5; color: #999; } /style七、Mermaid 图表插槽与动态组件关系图传递数据父组件子组件默认插槽 slot具名插槽 slot nameheader具名插槽 slot namefooter作用域插槽 slot :dataitem插入默认内容v-slot:header#footerv-slotprops动态组件 component :is组件A组件B组件CKeepAlive缓存组件状态onActivatedonDeactivated八、常见问题Q1为什么我的插槽内容没有显示检查以下几点子组件是否正确放置了slot标签具名插槽的名称是否匹配区分大小写父组件是否使用了template v-slot:name包裹内容Q2作用域插槽的数据如何解构!-- 解构并设置默认值 -- template #default{ user {}, index 0 } span{{ user.name }}/span /template !-- 重命名 -- template #default{ user: person } span{{ person.name }}/span /templateQ3KeepAlive 缓存不生效确保被缓存的组件有name选项用于include/exclude匹配KeepAlive直接包裹component :is...组件切换时确实是通过:is切换而不是v-ifscript // 为组件设置名称 export default { name: UserProfile // KeepAlive include/exclude 匹配这个名字 } /scriptQ4Vue3 中如何访问插槽内容import{useSlots,useAttrs}fromvueconstslotsuseSlots()constattrsuseAttrs()// 判断插槽是否存在consthasHeader!!slots.header// 渲染插槽内容slots.default?.()九、总结特性用途关键语法默认插槽向组件插入内容slot/ 组件标签内直接写内容具名插槽多位置内容分发slot namexxx/v-slot:xxx/#xxx作用域插槽子传父数据 自定义渲染slot :dataxxx/v-slotprops动态插槽名运行时决定插槽位置#[dynamicName]动态组件切换不同组件component :iscompKeepAlive缓存组件状态KeepAlive include...Vue3 统一了插槽的内部实现所有插槽都是函数带来了更好的性能和一致性。掌握插槽与动态组件是构建高复用组件库的关键。十、思考题实现一个Tabs组件使用具名插槽实现标签页内容分发支持v-model绑定当前标签。条件渲染插槽改造BaseCard组件当未提供header插槽时自动隐藏头部区域。KeepAlive 动态组件实现一个多步骤表单每步一个组件使用 KeepAlive 缓存各步骤的填写数据。递归组件 插槽实现一个可折叠的树形组件使用作用域插槽让父组件自定义每个节点的渲染方式。

相关新闻