5.3 组件进阶:插槽与动态加载 本节摘要:插槽解决"组件结构里留白给调用方填"的内容分发问题,动态组件与 keep-alive 解决"同一位置切换组件且保留状态",异步组件解决"用到才加载"。本节还给出第三方组件库的选型框架——它本质上是把这三件事委托给成熟实现。 学完这节你能做什么 阅读完本节,你应当能够: 区分默认插槽、具名插槽、作用域插槽的适用场景并写出用例; 用动态组件实现标签页切换,并说明 keep-alive 缓存与销毁的差异; 配置异步组件按需加载并处理加载态与失败态; 按团队与场景选择 UI 组件库而不是只看 star 数。 一、插槽:组件的内容进口 Props 传的是数据,插槽传的是结构。
本节摘要:插槽解决"组件结构里留白给调用方填"的内容分发问题,动态组件与 keep-alive 解决"同一位置切换组件且保留状态",异步组件解决"用到才加载"。本节还给出第三方组件库的选型框架——它本质上是把这三件事委托给成熟实现。
阅读完本节,你应当能够:
Props 传的是数据,插槽传的是结构。一个卡片组件:
<!-- Card.vue --> <template> <div class="card"> <div class="card-header"><slot name="header"></slot></div> <div class="card-body"><slot></slot></div> </div> </template>
调用方决定 header 和正文放什么:
<card> <template #header> <h3>订单详情</h3> </template> <p>订单内容正文</p> </card>
作用域插槽再进一步:留白的内容由调用方写,但数据由组件给。列表组件把每一项的数据回传给插槽:
<!-- ItemList.vue --> <template> <ul> <li v-for="item in items" :key="item.id"> <slot :item="item" :index="i_loop(item)"> {{ item.name }} <!-- 后备内容:调用方不填时的默认渲染 --> </slot> </li> </ul> </template>
<item-list :items="orders"> <template #default="{ item }"> <span class="tag">{{ item.status }}</span> {{ item.name }} </template> </item-list>

这套机制是组件库表格、下拉、级联组件灵活性的来源:库负责循环与交互,业务方全权决定每一行长什么样。判断什么时候需要作用域插槽有个简单标准——组件不知道、也不该知道要展示数据的哪些字段时。
标签页切换,朴素的写法是多个 v-if。更简洁的是动态组件:
<component :is="currentTab"></component> <script> import TabHome from './TabHome.vue'; import TabProfile from './TabProfile.vue'; // currentTab 在组件对象之间切换 </script>
默认行为下,切换等于卸载旧组件再挂载新组件——每次切回,表单里的半截输入、滚动位置全部归零。要保留状态,包一层 keep-alive:
<keep-alive :include="['TabHome', 'TabProfile']" :max="10"> <component :is="currentTab"></component> </keep-alive>
被缓存的组件多出两个钩子:activated(切回来时)与 deactivated(切走时)。include 限定缓存名单、max 控制缓存上限(超出按 LRU 淘汰),这两个参数在大列表标签页场景很关键——无脑全缓存,内存会稳步爬升。
从管线视角看 keep-alive 尤其有意思:它不是"不销毁 DOM"这么简单,而是把整个组件的 vnode 树连同渲染状态一起摘下来存好,切回时原样挂回——跳过了重新执行渲染函数与 patch 的全过程,是第 9 章性能工具箱里性价比最高的手段之一。
首屏不需要的组件(弹窗、富文本、图表),可以声明成异步组件,打bundle时自动分包、用到时才下载:
// Vue 3 import { defineAsyncComponent } from 'vue'; const HeavyEditor = defineAsyncComponent({ loader: () => import('./HeavyEditor.vue'), loadingComponent: Spinner, // 加载中占位 errorComponent: ErrorTip, // 失败兜底 delay: 200, // 200ms 内加载完就不显示 loading,避免闪烁 timeout: 10000 });
Vue 2 对应方案是组件选项里的工厂函数返回 Promise,或配合路由做懒加载(第 7 章展开)。工程上的常见组合拳:路由级懒加载按页面分包,组件级异步处理页面内的重块,两层配合能把首屏体积砍掉一大截。
组件库选型的评估框架,按权重排序:
| 维度 | 关注点 | 权重理由 |
|---|---|---|
| 维护活跃度 | 发版频率、issue 响应、Vue 3 支持质量 | 决定三年后项目还在不在维护 |
| 定制能力 | 主题变量覆盖深度、组件 API 开放度 | 决定设计稿还原成本 |
| 按需引入 | 是否支持 tree-shaking | 直接影响打包体积 |
| 生态完整度 | 表格、表单、日期等高频组件覆盖 | 决定要不要混用两个库 |
| 团队熟悉度 | 现有代码习惯 | 隐性成本最大的一项 |
Element Plus(Vue 3 生态事实标准,后台管理场景覆盖全)、Ant Design Vue(企业级中后台,规范严谨)、Naive UI(Vue 3 原生设计、TypeScript 友好)、Vuetify(Material 风格、组件密度高)是常见候选。我个人的排序逻辑:后台管理系统优先生态完整度选大库;面向 C 端、设计定制重的项目,考虑小库加自研组件的组合——大库的默认样式越"全",越难掰成你想要的样子。
引入组件库后有一条纪律:按需注册。全量引入 Element Plus 会让产物多出数百 KB;用其提供的按需引入插件后,只有用到的组件进入产物。这与第 3 章"编译期能省则省"的原则一脉相承。
自研的边界也顺带说清:日期选择器、虚拟滚动表格这类交互复杂的组件自研成本极高,买(用库)几乎总比造划算;而业务耦合强的展示组件(订单卡片、会员标签)自研反而干净——它们本来就不该出现在通用库里。
组件家族到此完整。下一章解决多组件共享状态——Vuex 与 Pinia。