在构建现代Web应用时React组件库无疑是开发者的得力助手,无论你是刚刚踏入前端开发的新人还是已经有一定经验的工程师,理解和掌握React组件库的设计与架构规划都是提升开发效率、增强项目可维护性的关键
目录
组件库设计与开发
设计与开发一个组件库不仅是一个技术挑战更是一次系统化思维的锻炼,从需求分析到架构设计再到组件的实现与优化,每一步都需要思考如何保持高效可扩展和易于维护,通过从零到一的开发实践,我们可以逐步完善并优化这个组件库并朝着像Ant Design那样的行业级组件库迈进,接下来简单的梳理一下开发组件库之前,对组件库的架构设计等内容的刨析:
总体架构
在开发组件前,我们要先了解一下开发组件的总体架构设计,主要分为以下几点:
分层设计:ant design强调组件库的模块化设计,可以将组件库划分为基础组件、复合组件、主题样式、业务组件等
antd就是通过rc-xxx来提供基础组件(unstyled component),它们只具备功能交互但不具备UI表现,复合组件就是各个组件之间的结合,如IconButton就是Icon组件和Button组件的结合
解耦原则:在设计组件库时需要确保各个组件之间的依赖关系尽量松散,以便组件能够灵活独立使用从而提高灵活性
对于每个组件都需要定义样式、ts类型、基础操作和工具方法等
响应式设计:考虑在当前前端开发的多平台需求,组件库需要支持响应式布局从而确保在移动端和桌面端的一致性
我们可以通过媒体查询、ResizeObsercer、Grid布局等操作实现响应式布局
状态管理:针对每个组件确定哪些是内部状态,哪些是受控状态。内部状态适用于组件本身控制的场景而受控状态则适用于需要父组件传值的场景。例如antd使用的是基于React的Context来处理跨组件的状态管理,避免在复杂场景下的prop drilling
状态管理主要分为两种情况,一种是全局状态:主要是用于基础配置、国际化配置、主题配置等操作,react主要通过Context、useSyncExternalStore来实现,vue是通过vue-demi实现;另一种是局部状态:主要是表单场景如受控和非受控(状态是否跟随表单值双向奔赴)。
样式主题设计:antd引入Token的概念来便于通过tokens控制组件库的整体样式风格,提升了主题化和定制化的灵活性,我们可以使用css-in-js实现动态样式,如Emotion、style-components等保证组件样式的隔离性和动态性,通过css变量和样式优先级控制来确保定制化过程中不破坏原有的设计系统
color tokens:颜色色值系统主要分为(antd、arco | mantineUI | mui | shadcn/ui),样式的模块化方案主要分为:css-in-js(弊端:运行时、ssr支持,如emotion、styled-components),module css。
模块设计
基础组件:基础组件应具备高度可复用性,尽量设计为无状态组件从而避免依赖特定业务逻辑,并且保持API简洁,确保组件的输入属性(props)具备一致性来便于开发者理解和使用。
高级组件:利用组合的方式如通过Render Props或者组件插槽来实现高度自定义的组件,需要明确父子组件的职责边界,例如在表单组件中字段组件负责输入控件,而表单组件负责整体数据管理。
工具模块:例如颜色处理、格式化等,可以提取成工具库进行独立维护,antd中这些工具函数放在@ant-design/utils包中便于复用,如骨架屏(Skeleton)、加载组件(Spin)等可以提高用户体验从而保持界面的连贯性。
选型落地
开发组件库之前,我们要先确定好开发组件库的一些技术选型,这个对于后期真正做组件库的项目开发影响是很大的,我们组件库做的怎么样很大程度上是受我们一开始技术选型的影响,这里我们可以从以下几个维度去思考一下组件库技术选型的开发:
1)包管理工具:pnpm workspace实现多包管理,提升安装速度并减少磁盘空间占用
2)组件库开发语言:TypeScript提供类型安全,增强代码健壮性并支持静态类型检查
3)文档与组件展示:dumi等实现构建文档站点,支持组件展示、API文档生成与实时预览
4)构建工具:TypeScript编译器,使用tsc进行代码编译,生成ES模块和CommonJs模块,保证兼容性。
5)样式管理:可选使用css-in-js如(Emotion、styled-components)实现动态样式和主题化
6)组件组织:按模块化组织、基础组件与复合组件分离,确保复用性和解耦性
7)测试框架:使用Jest+Testing Library进行单元测试和集成测试,确保组件库功能正确性
8)版本控制与CI/CD:配置GitHub Actions实现自动化测试和发布流程,确保代码提交后的质量
9)构建产物发布:使用pnpm publish发布构建产物,便于组件库的版本管理和分发
当我们确定好技术选型之后,后面就需要考虑如何将这些技术选型有效的进行落地实现,在设计组件库之前,我们需要先了解antd的目录结构和各模块的功能是很有帮助的,antd的源码目录结构大致分为以下几个主要部分:
components:核心组件模块,每个文件夹是一个独立的组件,包含实现代码、样式和文档等
rc-components:基础组件库,提供逻辑独立、无样式的基础功能组件,如rc-tree、rc-tabs等
styles:实现cssinjs,支持主题定制与动态切换
tools:构建和发布工具,antd-tools用于打包、构建、发布和测试自动化
scripts:构建和发布相关的脚本、例如自动化打包、文档生成、CI/CD脚本
docs:文档文件夹,主要用来管理组件库文档、演示代码和网站配置
tests:测试文件夹,包含单元测试和集成测试
开发流程
1)Monorepo管理:将所有相关组件工具、文档等放在一个仓库中,使用工具(如Lerna或Nx)来管理不同的包(如UI组件、主题、工具函数等),简化版本控制和依赖管理从而使得更新组件库的版本时不需要单独更新多个仓库
2)TypeScript支持:为每个组件编写类型声明提供了静态类型检查,帮助使用者了解如何调用组件、传递属性等,减少了运行时错误的可能性从而提高了开发的可靠性
3)测试工具链:使用Jest或Mocha进行单元测试,结合React Testing Library或Enzyme等工具测试组件之间的交互,确保每个组件的功能正确
4)CI/CD配置:使用GitHub Actions、CircleCI或Travis CI等工具来自动化代码的构建、测试和部署,当代码推送到仓库时CI工具自动运行测试,确保新提交的代码没有破坏已有功能,将组件库部署到npm、GitHub Package Registry等包管理平台,自动发布最新的版本确保开发人员可以快速使用到最新版本
5)版本管理:遵循语义化版本控制规范,使用major.minor.patch的版本号格式方便用户理解每次发布的变更范围,使用工具如standard-version或semantic-release结合Git commit信息自动生成版本号和更新日志,简化发布过程
像antd design的设计方式就是通过如下的方式进行:

模拟演示
这里我简单示例一下开发组件,项目开始第一步先初始化一个项目,终端执行如下命令开始执行:
pnpm init
后面就是对rollup相关的一些配置了,详情可以参考我之前的文章:地址 ,这里先把主要的一些配置插件下载,后面再根据具体需求进行调整吧,项目名称的话暂时取名react-library,后期如果发布npm包的话肯定是要调整的,这是后话,以下是初始化项目目前需要插件:
bash
{
"name": "react-library",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"type": "module",
"scripts": {
"dev": "rollup -c -w",
"build": "rollup -c",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.27.1",
"@babel/plugin-transform-runtime": "^7.27.1",
"@babel/preset-env": "^7.27.2",
"@babel/preset-react": "^7.27.1",
"@babel/preset-typescript": "^7.27.1",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-typescript": "^12.1.2",
"@types/react": "^19.1.4",
"rollup": "^4.41.0",
"rollup-plugin-dts": "^6.2.1",
"tslib": "^2.8.1"
},
"dependencies": {
"react": "^19.1.0"
}
}
为了兼容command语法,这里我们肯定是需要babel来进行转换的,所以这里我们先创建.babelrc文件提前配置好,如下所示:
bash
{
"presets": [
"@babel/preset-env",
"@babel/preset-typescript",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-transform-runtime"
]
}
接下来就是对rollup.config.js进行具体的配置了,因为我们开发的组件库肯定是需要ts的类型进行提示关键字的,所以这里我们将配置暴露出一个数组,然后暴露出一个核心代码文件以及类型声明文件,处理类型声明文件需要借助一个插件,终端执行如下命令进行安装:
bash
pnpm i rollup-plugin-dts -D
接下来我们开始对我们的项目进行如下的rollup的初始配置,如下所示:
javascript
import { babel } from '@rollup/plugin-babel'
import typescript from '@rollup/plugin-typescript'
import { dts } from "rollup-plugin-dts";
export default [
// 核心代码打包配置
{
// 打包入口文件路径
input: {
index: 'src/index.ts', // 多入口,设置多个打包入口文件路径
},
// 打包输出配置
output: {
dir: 'dist', // 设置打包后的文件目录,多入口
format: 'esm', // 设置打包格式为esm模块
name: 'bundleName', // 当format格式为iife和umd时,必须提供变量名
plugins: [], // 生成最后文件的插件列表,,例如对打包后的文件进行压缩等操作
},
// 插件列表,例如对打包后的文件进行压缩等操作
plugins: [
// Babel 配置
babel({
babelHelpers: 'runtime',
exclude: ['node_modules/**'], // 排除node_modules目录下的@vueuse模块,避免重复打包
extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue'], // 指定需要转换的文件扩展名
babelrc: true // 启用.babelrc 配置,避免重复配置插件
}),
// TypeScript 配置
typescript(),
],
// 外部依赖配置,避免打包第三方库
external: ['react'],
},
// 类型声明文件打包配置
{
input: 'src/index.ts',
output: {
file: 'dist/index.d.ts',
format: 'esm',
},
plugins: [dts()],
external: [/\.css$/], // 排除 CSS 文件
},
]
当然涉及ts的配置,也就是tsconfig.json文件的内容,博主也进行了如下配置:
bash
{
"compilerOptions": {
"target": "es6", // 编译目标为ES6
"jsx": "react", // 允许JSX语法在TS文件中使用
"module": "esnext", // 指定生成代码的模块系统
"moduleResolution": "Bundler", // 指定模块解析策略
"resolvePackageJsonExports": true, // 支持从package.json的exports字段导入模块
"skipLibCheck": true, // 跳过所有声明文件的类型检查
"esModuleInterop": true, // 允许从默认导出导入模块
"forceConsistentCasingInFileNames": false, // 禁止对同一个文件的不一致的引用
"allowSyntheticDefaultImports": true, // 允许从没有默认导出的模块中默认导入
"strict": true, // 启用所有严格类型检查选项
"strictNullChecks": true, // 启用严格的null检查
"noImplicitAny": true, // 禁止隐式any类型
"noUnusedLocals": true, // 报告未使用的局部变量为错误
"noUnusedParameters": true, // 报告未使用的参数为错误
"stripInternal": true, // 移除所有内部属性
"allowJs": false, // 允许编译JS文件
"lib": ["ESNext", "DOM"], // 指定要包含在编译中的库文件列表
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "**/*.ts"]
}
接下来我们创建一个components文件夹,里面就存放后期所有要设置的组件,并且设置其对应的样式和对应的类型声明文件,然后写完代码之后就在index.tsx中将当前组件及声明文件暴露出去:

配置完成之后,我们终端执行 pnpm run dev 进行打包操作,如下所示可以看到外面的ts文件已经成功被编译成js文件并且也生成了对应的类型声明文件:

接下来我们开始使用一下我们封装的组件,终端执行 npm link 将当前库挂载到全局,然后我们可以创建另外一个项目用于测试我们开发的组件库,终端执行如下命令来安装我们挂载的全局包:
npm link <你的组件库名称>
// 博主设置的名称为react-library,终端执行如下命令安装
npm link react-library
安装完全局包之后,这里我们直接引入我们封装的按钮组件,然后传入children内容,可以看到我们的按钮已经成功的被加载出来了:

即使是一个简单的组件如按钮,当我们封装UI组件的时候,也要考虑太多的东西在里面,后面维护的UI组件复杂之后,组件考虑的东西也随着增多,本篇希望给大家开发UI组件提供一个简单的思路脉络吧,刻意练习,大部分人都有舒适区、提倡大家尽量往舒适区边缘去拓展一下,多给自己一点滤镜,多给自己一些挑战才有帮助,如果说一直每天写业务,写一写基本开发,那其实提升不大,后面有时间博主开始真正对组件库方面进行实战开发!