组件库架构设计与技术选型
Vue3组件库的核心竞争力不在组件数量,而在类型系统完整度、响应式布局适配范围、以及主题定制能力。技术栈选型直接决定组件库的上限。
推荐技术栈组合:
# 组件库工程化配置
build: Vite 5.x + Rollup
style: UnoCSS (原子化CSS) + CSS Variables (主题切换)
type: TypeScript 5.4 + vue-tsc
lint: ESLint 9 + @stylistic/eslint-plugin
format: Prettier 3
test: Vitest + @vue/test-utils
docs: VitePress
TypeScript类型系统设计
组件库的类型设计决定开发者体验。泛型组件的类型推导比手动标注类型注解更有价值——用户不需要写额外类型就能获得完整的类型提示。
// 通用泛型组件类型定义
// src/types/components.ts
// 表格组件泛型:从数据行类型推导列定义
type TableColumn<T extends Record<string, any>> = {
key: keyof T
title: string
width?: number
sortable?: boolean
render?: (value: T[keyof T], row: T, index: number) => VNode
}
type TableProps<T extends Record<string, any>> = {
data: T[]
columns: TableColumn<T>[]
rowKey: keyof T
loading?: boolean
stripe?: boolean
onRowClick?: (row: T, index: number) => void
}
// 使用示例:TypeScript自动推导columns的key类型
type User = { id: number; name: string; role: 'admin' | 'user' }
// columns的key只能是'id' | 'name' | 'role',拼写错误立即报错
const columns: TableColumn<User>[] = [
{ key: 'id', title: 'ID', width: 80 },
{ key: 'name', title: '用户名' },
{ key: 'role', title: '角色', sortable: true },
// @ts-expect-error - 'age' 不在 User 类型中
{ key: 'age', title: '年龄' }
]
组件Props类型约束与提取
使用defineComponent和PropType时,类型信息会丢失。Vue3.3+引入了泛型组件,推荐直接用泛型写法:
// 泛型组件写法(Vue3.3+)
// src/components/YunButton/YunButton.vue
<script setup lang="ts" generic="T extends string | number">
import { computed, useSlots } from 'vue'
type ButtonSize = 'small' | 'medium' | 'large'
type ButtonType = 'primary' | 'secondary' | 'danger' | 'ghost'
const props = withDefaults(defineProps<{
size?: ButtonSize
type?: ButtonType
disabled?: boolean
loading?: boolean
icon?: string
}>(), {
size: 'medium',
type: 'primary',
disabled: false,
loading: false,
})
const emit = defineEmits<{
click: [event: MouseEvent]
}>()
const slots = useSlots()
const hasIcon = computed(() => props.icon || slots.icon)
</script>
// 类型提取工具:从组件实例提取Props类型
// src/types/extract.ts
type ExtractProps<T> = T extends new () => { $props: infer P } ? P : never
// 使用
type ButtonProps = ExtractProps<typeof YunButton>
// 结果:{ size?: ButtonSize; type?: ButtonType; ... }
响应式布局系统的CSS变量方案
组件库的响应式布局分两个层次:组件自身自适应(如表格列宽百分比)、容器断点适配(如侧边栏折叠)。核心机制是CSS Variables + Container Queries。
// src/styles/variables.css
:root {
/* 断点变量 */
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
--breakpoint-xl: 1280px;
/* 间距变量 - 响应式阶梯 */
--space-xs: 4px;
--space-sm: 8px;
--space-md: 16px;
--space-lg: 24px;
--space-xl: 32px;
/* 组件尺寸变量 */
--input-height-sm: 28px;
--input-height-md: 36px;
--input-height-lg: 44px;
}
/* 容器查询 - 组件根据父容器宽度自适应 */
.yun-table-wrapper {
container-type: inline-size;
container-name: table;
}
@container table (max-width: 600px) {
.yun-table__row {
flex-direction: column;
}
.yun-table__cell {
padding: var(--space-xs) 0;
}
}
前端工程化:组件按需加载与Tree-shaking
组件库必须支持Tree-shaking,否则全量引入会大幅增加打包体积。Vite环境下使用unplugin-vue-components自动按需引入:
// vite.config.ts
import Components from 'unplugin-vue-components/vite'
import { YunResolver } from '@yunthe/ui/resolver'
export default defineConfig({
plugins: [
vue(),
Components({
resolvers: [YunResolver()],
dts: 'src/components.d.ts',
}),
],
})
// 组件入口 - 独立导出保证Tree-shaking
// src/components/index.ts
export { default as YunButton } from './YunButton/YunButton.vue'
export { default as YunTable } from './YunTable/YunTable.vue'
export { default as YunForm } from './YunForm/YunForm.vue'
// 每个组件附带独立的CSS文件
export './YunButton/style.css'
export './YunTable/style.css'
export './YunForm/style.css'
跨端小程序开发的组件适配
组件库如果需要同时覆盖Web和小程序端,推荐使用Taro或uni-app的组件封装层,而非重新开发一套小程序组件。核心思路是:逻辑层复用Vue3 Composition API,渲染层通过条件编译切换模板:
// src/components/YunButton/adapter.ts
import { h, type VNode } from 'vue'
// #ifdef MP-WEIXIN
export const renderButton = (props: ButtonProps, slots: any): VNode =>
h('button', {
class: ['yun-btn', `yun-btn--${props.type}`, `yun-btn--${props.size}`],
disabled: props.disabled || props.loading,
openType: props.openType, // 小程序特有属性
}, slots)
// #endif
// #ifdef H5
export const renderButton = (props: ButtonProps, slots: any): VNode =>
h('button', {
class: ['yun-btn', `yun-btn--${props.type}`, `yun-btn--${props.size}`],
disabled: props.disabled || props.loading,
onClick: (e: MouseEvent) => emit('click', e),
}, slots)
// #endif
Flutter移动端的方案则是通过dart:js_interop桥接到Web组件库,适合需要原生渲染性能的场景。组件库设计时预留好adapter层,跨端适配的工作量就能控制在渲染层,而不用重写业务逻辑。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-jian-ku-she-ji-typescript-shi-zhan-yu-xiang-ying/