组件库开发实战:从 0 到 1 搭建企业级组件库

news/2024/12/12 13:11:28/文章来源:https://www.cnblogs.com/yuanyanglu/p/18602215

深夜,我盯着屏幕上的代码发呆。作为一个中型创业公司的前端负责人,我正在思考一个问题:我们是否需要开发自己的组件库?

过去一年,随着业务的快速发展,前端团队从 3 人扩展到了 12 人,项目也从 1 个变成了 6 个。每个项目都在用着不同版本的 UI 组件,设计规范也不统一,这让产品同学苦不堆言。经过一周的调研和团队讨论,我们决定开发一套自己的组件库。

项目规划

首先从业务目标出发,我们希望通过组件库实现三个核心目标:统一各个项目的设计规范、提高团队的开发效率、保证代码的质量标准。这些都是困扰我们团队已久的问题。

在技术层面,我们对组件库提出了严格的要求:必须有完整的 TypeScript 类型支持,确保类型安全;测试覆盖率要达到 100%,保证组件的可靠性;文档和示例要详细完整,降低使用门槛;同时要注重性能优化,保证组件的运行效率。

关于组件的范围,我们计划分四个层次逐步实现:

  • 基础组件:包括 Button、Input、Select、Table 等最常用的基础组件
  • 表单组件:包括 Form、DatePicker、Upload 等数据录入组件
  • 反馈组件:包括 Modal、Message、Notification 等交互反馈组件
  • 业务组件:包括 SearchForm、DetailCard、StatusFlow 等业务相关组件

这样的规划既照顾到了基础需求,又为未来的业务扩展预留了空间。我们决定采用渐进式开发策略,先完成基础组件和表单组件,然后再逐步扩展到反馈组件和业务组件。

技术选型

  • 在技术选型上,我们经过反复讨论和评估,最终确定了一套完整的技术栈。
  • 首先是基础框架的选择。考虑到团队的技术背景和项目需求,我们选择了 React 18 作为核心框架。样式解决方案上,我们采用了 Tailwind CSS 配合 CSS Modules 的组合,这样既能保证样式的可维护性,又能避免样式冲突。构建工具选择了 Vite,它的快速启动和热更新特性能大大提升开发效率。测试框架则采用了 Vitest 配合 Testing Library,这个组合既保证了测试的可靠性,又与 Vite 完美集成。
  • 在开发工具链方面,我们也做了精心的选择。文档系统采用了 Storybook,它不仅能够独立开发和测试组件,还能自动生成交互式文档。代码规范通过 ESLint 和 Prettier 的组合来保证,这样能确保团队代码风格的一致性。版本管理选择了 Changesets,它能够帮助我们更好地管理包的版本和变更日志。最后,我们使用 GitHub Actions 搭建了完整的 CI 流程,确保代码质量和发布流程的规范性。

组件开发

让我们从一个基础的 Button 组件开始:

// components/Button/Button.tsx
import { forwardRef } from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/utils'const buttonVariants = cva('inline-flex items-center justify-center rounded-md font-medium transition-colors', {variants: {variant: {primary: 'bg-primary text-white hover:bg-primary/90',secondary: 'bg-secondary text-white hover:bg-secondary/90',outline: 'border-2 border-primary text-primary hover:bg-primary/10'},size: {sm: 'h-8 px-3 text-sm',md: 'h-10 px-4 text-base',lg: 'h-12 px-6 text-lg'}},defaultVariants: {variant: 'primary',size: 'md'}
})interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {loading?: booleanicon?: React.ReactNode
}const Button = forwardRef<HTMLButtonElement, ButtonProps>(({ className, variant, size, loading, icon, children, ...props }, ref) => {return (<button ref={ref} className={cn(buttonVariants({ variant, size }), className)} disabled={loading || props.disabled} {...props}>{loading && <LoadingSpinner className='mr-2 h-4 w-4 animate-spin' />}{icon && <span className='mr-2'>{icon}</span>}{children}</button>)
})Button.displayName = 'Button'export { Button, buttonVariants }

测试用例

每个组件都需要完整的测试用例:

// components/Button/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { Button } from './Button'describe('Button', () => {it('renders children correctly', () => {render(<Button>Click me</Button>)expect(screen.getByText('Click me')).toBeInTheDocument()})it('handles different variants', () => {const { rerender } = render(<Button variant='primary'>Primary</Button>)expect(screen.getByText('Primary')).toHaveClass('bg-primary')rerender(<Button variant='secondary'>Secondary</Button>)expect(screen.getByText('Secondary')).toHaveClass('bg-secondary')})it('shows loading state', () => {render(<Button loading>Loading</Button>)expect(screen.getByRole('button')).toBeDisabled()expect(screen.getByText('Loading')).toBeInTheDocument()expect(screen.getByTestId('loading-spinner')).toBeInTheDocument()})it('forwards ref correctly', () => {const ref = jest.fn()render(<Button ref={ref}>Click me</Button>)expect(ref).toHaveBeenCalledWith(expect.any(HTMLButtonElement))})
})

文档系统

好的文档对组件库至关重要:

// stories/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react'
import { Button } from '@/components/Button'const meta: Meta<typeof Button> = {title: 'Components/Button',component: Button,tags: ['autodocs'],argTypes: {variant: {control: 'select',options: ['primary', 'secondary', 'outline']},size: {control: 'select',options: ['sm', 'md', 'lg']}}
}export default metatype Story = StoryObj<typeof Button>export const Primary: Story = {args: {children: 'Primary Button',variant: 'primary'}
}export const WithIcon: Story = {args: {children: 'With Icon',icon: <Icon name='plus' />,variant: 'primary'}
}export const Loading: Story = {args: {children: 'Loading',loading: true}
}

发布流程

建立规范的发布流程很重要:

// scripts/release.ts
import { getChangedPackages, createChangeset, publishPackages } from '@changesets/cli'async function release() {// 获取变更的包const changedPackages = await getChangedPackages()// 创建变更集await createChangeset({summary: 'Update components and fix bugs',packages: changedPackages.map(pkg => ({name: pkg.name,type: 'patch' // major | minor | patch}))})// 发布包await publishPackages({tag: 'latest',access: 'public',registry: 'https://registry.npmjs.org'})
}release().catch(console.error)

实践心得

开发组件库的过程中,我们总结出几点重要经验:

  1. 组件设计要兼顾灵活性和易用性
  2. TypeScript 类型定义要完整准确
  3. 文档和示例要详细易懂
  4. 测试用例要覆盖各种场景
  5. 版本管理要规范严谨

就像搭建一座大楼,组件库需要有坚实的基础(架构设计),规范的施工流程(开发规范),以及完善的配套设施(文档测试)。只有这样,才能建造出一个稳固耐用的组件库。

写在最后

开发组件库是一个持续演进的过程,需要在实践中不断完善。正如建筑需要经常维护一样,组件库也需要持续的更新和优化。

有什么问题欢迎在评论区讨论,让我们一起探讨组件库开发的最佳实践!

如果觉得有帮助,别忘了点赞关注,我会继续分享更多实战经验~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/851348.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker部署Mikochi,轻松管理文件上传下载

1.基本条件 (1)准备一台服务器 (2)部署docker、docker-compos服务 (3)创建数据储存目录mkdir -p /data/mikochi/data 2.部署mikochi[root@localhost mikochi]# cat docker-compose.yaml version: 3.7services:mikochi:image: zer0tonin/mikochi:1.7.0container_name: m…

[QT] MAC使用Qt Creator运行程序如何仅运行一个进程?

问题背景刚开始在 Mac 使用 QT Creator 运行项目时会发现每次 Run 程序都出现一个新的任务进程,而非类似 Windows 环境下是先 stop 之前的进程再创建。那么如何每次run后,就关闭上一次的进程,而重新拉起新进程呢? 解决方案

Windows 配置自动更新重启策略

I. 打开策略编辑器 【Win + R】打开 “运行” 窗口,输入: gpedit.msc打开“本地组策略编辑器”。 II. 设置不自动重启 启用策略,选择在你任何想要重启的时候重启计算机。III. 重启计算机 重启计算机,完成配置。

笔记本电脑蓝屏 硬盘损坏数据恢复

当笔记本电脑出现蓝屏故障,并且怀疑硬盘已损坏需要恢复数据时,可以参考以下步骤和建议: 一、初步处理 断开电源:在尝试任何数据恢复操作之前,首先要断开笔记本电脑的电源,以避免进一步的数据损坏或丢失。 评估蓝屏原因:蓝屏可能是由驱动程序错误、系统文件损坏、硬件故障…

.NET Core 堆结构(Heap)底层原理浅谈

https://www.cnblogs.com/lmy5215006/p/18583743 .Net托管堆布局加载堆 主要是供CLR内部使用,作为承载程序的元数据。HighFrequencyHeap存放CLR高频使用的内部数据,比如MethodTable,MethodDesc.通过is判断类型之间的继承关系,调用接口的方法和虚方法,都需要访问MethodTable…

简化版 先求每个商品品类中亏损的最大的 写入新的表中

import pandas as pd # 读取原始表 简化为仅求亏损最大的 # 路径需要双斜杠 data = pd.read_excel(D:\\work\\2\\配料统计表.xlsx,sheet_name=Sheet1) # 对数据做处理 #第一步 找到亏损类和涨出类 如果金额大于0 是亏损;否则是涨出 data_loss= data[data[差异金额]>0] …

ABB IRB4400机器人示教器维修黑屏问题

当ABB机器人IRB4400的示教器出现黑屏问题时,可能的原因包括硬件故障、软件冲突或外部干扰。以下是一些可能的解决方法:硬件故障检查:检查示教器显示屏是否损坏或老化。检查与显示屏连接的电缆或电路板是否出现故障。更换损坏的部件。软件冲突检查:检查示教器的操作系统或应…

使用正点原子的直流无刷驱动板自写FOC控制永磁同步(PMSM)电机(开环位置)

由于ST官方MotorControlWorkbench生成的FOC代码过于复杂,决定自己使用正点原子的直流无刷驱动板自己编写FOC去控制PMSM电机。FOC代码参考的是灯哥的教材DengFOC官方文档。 1、配置TIM1高级定时器 2、foc.c代码/** foc.c** Created on: Dec 11, 2024* Author: ME-LZQ*/#i…

【Office Access 2024软件下载与安装教程】

1、安装包 「Office LTSC 2024」: 链接:下载地址2、安装教程(建议关闭杀毒软件和系统防护) 1) 下载并解压下载的安装包,双击Setup.exe安装,弹窗安装对话框2) 只留Access选项,点击一键安装3) 保持联网状态 部分在线下载更新4) 安装完成后,解压…

冬季节假日跨境电商运营压力大,哪六款软件能提升效率?

在跨境电商行业,冬季节假日的订单高峰期犹如一场紧张而关键的战役。每一个环节都需要紧密衔接、高效运转,才能在汹涌的订单浪潮中乘风破浪,收获丰硕成果。对于 J 人主导的跨境电商团队公司而言,可视化团队协作办公软件就如同战场上的精良武器,助力团队精准作战,高效协同。…

年底了,产品经理如何写好一份年终工作报告?

项目管理软件在产品经理年终汇报中发挥着关键作用。它不仅帮助产品经理高效整理和展示数据,提高汇报的准确性和逻辑性;还通过实时同步项目进度和风险管理功能,帮助产品经理及时发现问题并采取措施;同时,通过制定详细的汇报大纲和数据驱动的汇报方式,提高了汇报的专业性和…