07|组件库与设计系统
第九章 / 07。目标:从真实组件合同与产物证据选库,不按截图、热度或组件数量投票。
先做一条垂直切片
用同一个“异步提交表单”检验候选库:label 与错误关联、键盘提交、焦点恢复、loading/disabled、主题 token、SSR 输出、按需导入和测试定位。一个 Button demo 证明不了表单、弹层和日期选择器边界。
<script setup lang="ts">
const props = defineProps<{ pending: boolean }>()
const emit = defineEmits<{ submit: [] }>()
</script>
<template>
<VendorButton
:loading="pending"
:disabled="pending"
@click="emit('submit')"
>
<slot />
</VendorButton>
</template>业务只依赖 AppSubmitButton 的小合同。若替换 VendorButton,调用页面和测试不必知道第三方 prop 名。但不要给每个基础组件都包一层空壳;只有需要统一语义、主题或迁移隔离时才值得。
打包身份与 CSS 都要验证
开发组件库时,Vue 通常是 peerDependencies,并在库构建中 external,避免应用出现两份 Vue 运行时:
export default defineConfig({
build: {
lib: { entry: 'src/index.ts', formats: ['es'] },
rolldownOptions: { external: ['vue'] }
}
})Vite 8 使用 rolldownOptions;旧主版本常见 rollupOptions,维护旧项目时应按锁定版本核对。消费端实验要检查 npm ls vue/产物图、实际 import 后的 JS/CSS、主题样式顺序和 SSR hydration;“支持 tree shaking”只是声明,实际增量由导入方式和构建配置决定。
评估边界
| 维度 | 可验收证据 |
|---|---|
| 无障碍 | 键盘路径、role/name、焦点 trap/恢复、错误关联 |
| 主题 | token 覆盖范围、暗色/高对比、升级 diff |
| SSR | 服务端无 browser global,首轮输出一致 |
| 维护 | 版本策略、变更日志、Vue peer 范围、安全响应 |
| 体积 | 真实应用 production build 的增量,不引用官网固定数字 |
先固定上游接口,再写适配器
适配不是给相同属性换一个名字就结束。假设候选供应方约定 visible 输入,dismiss/accept 输出:业务的 open 要映射 visible,dismiss 变 update:open(false),accept 在 pending=false 才允许变 confirm。焦点何时进入、关闭后回到哪里属于业务组件的合同,不能让调用页依赖供应方内部 class。
本节练习先用答案提供的 VendorDialog.vue 小替身固定这个协议,因此不需要先选或安装某个 UI 库。替身没有真实 focus trap;选定生产库后,用同一测试再验证库的焦点限制、Portal/Teleport、滚动锁和原生键盘行为。把替身通过当成该库已经通过是错误推论。
练习:做一个可替换适配器
实现 AppDialog 合同:open prop、update:open 与 confirm 事件;打开后聚焦标题,Escape 关闭,关闭后焦点回到触发按钮,pending 是布尔 prop,true 时 confirm 不得发出;false 时一次点击恰发一次。先针对 visible/dismiss/accept 供应方协议与答案给出的替身写适配器和组件测试,再替换成实际候选库复核;测试只能依赖 AppDialog 合同,不得读取 Vendor 私有 class。
参考答案:第 9 章第 07 题。