Skip to content

组件库设计和实现

1. 你在组件库中负责什么?

我是核心成员,主要负责:

  • 复杂组件的设计实现(如瀑布流、车牌号组件)
  • 组件库工程化建设(文档站、按需加载、CI 流程、测试)
  • 代码审查和 issue 处理,把控组件质量
  • 优化组件、讨论新组件需求

2. 组件库设计思想

目标:提效、体验一致、可维护

原则

  • API 简单直观,复杂场景可扩展
  • 高内聚低耦合,组件间通过 props/事件通信
  • 向下兼容,破坏性变更要有 deprecate 方案
  • 支持主题定制、全局配置

架构分层

  • 基础层:设计变量、基础样式、动画
  • 工具层:工具函数、通用 hooks
  • 基础组件:Button、Input、Toast、Dialog 等
  • 业务组件:表单封装、表格封装等
  • 工程层:文档、按需加载、测试、CI

3. 组件封装与二次封装技巧(重点)

3.1 封装通用组件的考量

  • 通用性:提炼核心功能,避免耦合具体业务逻辑
  • API 设计:Props 命名规范,提供合理的默认值和类型校验
  • 可定制性:通过插槽(Slots)和 CSS 变量支持自定义内容和样式
  • 受控与非受控:支持 v-model 双向绑定,也支持默认状态
  • 兼容性、拓展性,方便升级
  • 清晰的文档,提供示例和 API 文档、注释、类型定义

3.2 二次封装第三方组件(如基于 AntD/El 封装业务组件)

Q: 如何透传属性和事件? 使用 v-bind="$attrs" 将父组件传递的非 Props 属性和事件直接透传给内部第三方组件。

Q: 如何透传插槽? 遍历 $slots 动态渲染,确保第三方组件的插槽能正常使用。

vue
<template>
  <el-input v-bind="$attrs">
    <!-- 插槽透传见下文 -->
  </el-input>
</template>

<template>
  <el-table v-bind="$attrs">
    <template v-for="(val, key) in $slots" #[key]="scope">
      <slot :name="key" v-bind="scope" />
    </template>
  </el-table>
</template>

Q: 如何暴露内部组件的方法(Ref)? 使用 defineExpose 暴露内部组件实例,否则父组件无法调用第三方组件的原生方法(如 focus, validate)。

ts
const tableRef = ref();
defineExpose({
  // 暴露 tableRef 的方法
  ...tableRef.value,
});

5. 代表性组件:瀑布流

核心方案

异步队列 + 绝对定位。维护列高度数组,找最短列放入新卡片,用 transform: translate3d GPU 加速。

核心难点:图片高度不确定

设计 layoutQueue 队列,串行等待图片加载完成后再排版,配合超时和错误兜底,防止布局错乱。

关键实现

  • 布局算法:维护 columns 数组记录每列高度,找最小高度列放入,更新列高,容器高度取最大值
  • 异步队列:新 Item 进入队列,等待图片 onload 获取真实高度后计算位置,设置 maxWait 超时机制
  • 错误处理:5 种策略(默认高度/占位图/重试/完整降级/跳过),确保异常场景可用性
  • 性能优化shallowReactive 减少响应式开销,16ms 防抖,增量更新避免重复计算
  • 生命周期:监听页面 onShow/onHide,页面隐藏时中断排版,恢复时继续,避免内存泄漏

成果

  • 支持动态增删、自动加载、跨平台适配
  • 500+ 项目场景下重排耗时 < 100ms

6. 踩过的坑

跨端兼容:同一样式在小程序和 H5 表现不一致,后来抽象适配层统一管理差异。

API 设计:早期 API 太针对具体场景,后面需求多了只能堆 props。后来在不破坏兼容的前提下,增加更通用的配置方式,通过 deprecate 提示引导迁移。

7. 一句话总结

理解组件库分层架构和设计原则,有从 0 到 1 实现复杂组件(瀑布流)的完整经验,关注性能、稳定性和跨端适配。