3、目录结构设计:组件库的目录规范、src目录划分、构建输出目录规划
说实话,目录结构这事儿,看着不起眼,但踩过的坑是真不少。
我记得刚带团队那会儿,有个同事把组件和工具函数全塞在一个文件夹里。三个月后,项目膨胀到连他自己都找不到文件在哪儿。嗯,那场面,挺尴尬的。
所以这一章,我想跟你聊聊我这些年沉淀下来的目录设计思路。说白了,就是让你少走弯路。
3.1 顶层目录:一眼看清项目骨架
一个组件库的根目录,应该像人的骨架一样清晰。我个人习惯这样组织:
my-component-library/
├── packages/ # 多包管理(如果采用monorepo)
├── src/ # 源码目录
├── dist/ # 构建输出
├── docs/ # 文档
├── scripts/ # 构建脚本
├── tests/ # 测试
├── .eslintrc.js # 代码规范
├── .prettierrc # 格式化配置
├── tsconfig.json # TypeScript配置
├── package.json
└── README.md
你可能会问:为什么要把 scripts 单独拎出来?
我在项目中遇到过,有人把构建逻辑写在 package.json 里,结果脚本越来越长,最后根本没法维护。单独放一个文件夹,每个脚本职责清晰,想改哪里直接定位。
3.2 src目录划分:组件库的心脏
src 目录是组件库的核心。怎么划分,直接决定了后续的可维护性。
我推荐这种结构:
src/
├── components/ # 组件目录
│ ├── Button/
│ ├── Input/
│ └── index.ts # 统一导出
├── hooks/ # 自定义hooks
├── utils/ # 工具函数
├── styles/ # 全局样式
├── types/ # 类型定义
├── locale/ # 国际化
└── index.ts # 入口文件
3.2.1 components:每个组件一个文件夹
为什么每个组件要单独一个文件夹?
你想想看,一个成熟的组件,除了核心代码,还有样式、测试、类型、文档。如果全堆在一个文件里,那画面太美我不敢看。
以 Button 组件为例:
components/Button/
├── index.tsx # 组件实现
├── style.ts # 样式(如果用CSS-in-JS)
├── Button.test.tsx # 单元测试
├── index.md # 组件文档
└── types.ts # 类型定义
3.2.2 hooks:逻辑复用的秘密武器
我见过很多新手,把逻辑直接写在组件里。结果一个组件上千行,改一个功能要翻半天。
其实,把可复用的逻辑抽成 hooks,是性价比最高的优化。比如:
hooks/
├── useClickOutside.ts # 点击外部检测
├── useDebounce.ts # 防抖
├── useEventListener.ts # 事件监听
└── index.ts
我曾经在一个项目中,把表单校验逻辑抽成 useFormValidator,结果后面三个页面直接复用,省了至少两天工时。
3.2.3 utils:纯函数的天堂
工具函数,说白了就是那些不依赖 React 的纯函数。比如日期格式化、classNames 合并、DOM 操作辅助。
utils/
├── classNames.ts # 类名合并
├── dom.ts # DOM操作
├── format.ts # 格式化
└── index.ts
3.3 构建输出目录规划:让用户用得舒服
构建输出,是用户直接使用的部分。规划得好,用户开心;规划得乱,用户骂娘。
我建议的输出结构:
dist/
├── es/ # ES Module 格式
│ ├── Button/
│ ├── Input/
│ └── index.js
├── lib/ # CommonJS 格式
│ ├── Button/
│ ├── Input/
│ └── index.js
├── types/ # TypeScript 类型声明
│ ├── Button/
│ └── index.d.ts
└── index.css # 全局样式
3.3.1 为什么需要两种模块格式?
这个问题我经常被问到。
简单说:es 给现代打包工具(如 Vite、Webpack 5)用,支持 tree-shaking;lib 给 Node.js 环境或旧版打包工具用。
在 package.json 中这样配置:
{
"main": "dist/lib/index.js",
"module": "dist/es/index.js",
"types": "dist/types/index.d.ts"
}
3.3.2 按需加载:别让用户全量引入
我记得有个用户反馈,说引入一个 Button,结果打包出来 200KB。一查,原来是全量引入了所有组件。
解决方案很简单:每个组件单独导出,同时提供 es 目录下的路径。
// 用户按需引入
import Button from 'my-lib/es/Button'
package.json 中配置 sideEffects,导致样式被 tree-shaking 干掉。记得加上 "sideEffects": ["*.css"]。
3.4 目录规范总结
说了这么多,我整理了一张表,方便你对照:
| 目录 | 用途 | 注意事项 |
|---|---|---|
| src/components | 组件实现 | 每个组件独立文件夹 |
| src/hooks | 逻辑复用 | 保持单一职责 |
| src/utils | 工具函数 | 不要混入业务逻辑 |
| dist/es | ES Module 输出 | 支持 tree-shaking |
| dist/lib | CommonJS 输出 | 兼容 Node.js |
| dist/types | 类型声明 | 必须与源码同步 |
嗯,目录结构这块,其实没有绝对的标准。但有一条铁律:让新人看一眼就能找到文件,让机器一眼就能识别格式。
你按照这个思路去设计,至少能保证项目在半年后,你自己还能看得懂。