组件应按业务功能域而非 UI 类型拆分,如电商场景用 ProductCard、CartBadge、CheckoutStep;需严格隔离模块边界、精确控制导出、采用 CSS-in-JS 或 CSS Modules 实现样式隔离,并确保类型定义随组件发布且无交叉引用。

组件按功能域拆分,而不是按 UI 类型
很多人一上来就建 Button、Input、Modal 这类目录,结果半年后发现所有组件都依赖同一套主题逻辑,改个颜色要全局 grep 十次。真正可扩展的模块化,是从业务语义出发切分——比如电商场景下,ProductCard、CartBadge、CheckoutStep 才是自然边界。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 每个模块目录包含自己的
index.ts(导出公共 API)、types.ts(仅本模块用的类型)、hooks/(非通用逻辑不抽到 shared) - 禁止跨模块直接 import 组件实现文件,只允许 import 对方的
index.ts导出项 - 共用工具函数必须进
shared/utils,且要带单元测试,否则很快变成“谁都改、谁都不敢删”的黑盒
构建时按需导出,别让 tree-shaking 失效
写 export * from './Button' 看起来干净,但 Webpack/Vite 会把整个 Button 目录所有依赖(包括图标、动效、polyfill)全打进 bundle。用户只用一个 PrimaryButton,却要下载整套按钮体系。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 每个组件模块的
index.ts只导出明确对外暴露的组件和类型,禁用export * - 使用
package.json的exports字段精确控制入口,例如:"exports": { ".": "./dist/index.js", "./button": "./dist/button/index.js", "./button/primary": "./dist/button/primary.js" } - 在
tsconfig.json中设"declaration": true,否则下游项目无法正确推导类型,导致不得不any掉
主题与样式隔离必须靠 CSS-in-JS 或 CSS Modules
纯 CSS 文件 + BEM 命名?上线后就会发现:设计改个圆角,你得改 17 个组件的 scss 文件;换套暗色主题,得手动维护两套 class 名映射表。CSS-in-JS(如 @emotion/styled)或 CSS Modules 才能保证样式作用域和变量注入可控。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 每个组件目录下放
styles.ts(用css函数写样式对象),不放.scss - 主题变量统一由
shared/theme提供,通过 React Context 注入,禁止组件内硬编码#3b82f6 - 避免在
styled.div里写媒体查询,提取成独立的useBreakpoint()hook,方便 SSR 和测试
类型定义必须随组件一起发布,且禁止交叉引用
很多团队把类型全扔进 types/index.d.ts,结果改个 ButtonProps 就触发全量重编译;更糟的是,CartBadge 的类型里引用了 ProductCard 的接口,形成隐式耦合——某天想把购物车模块单独 npm 发布,直接失败。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 每个模块的
types.ts只定义自己需要的类型,外部类型一律用import type显式引入 - 构建时用
tsc --emitDeclarationOnly单独生成.d.ts,并确保package.json的"types"指向正确路径 - CI 流程中加一步
npm pack --dry-run校验最终发布的包是否含.d.ts且无node_modules引用残留
最常被忽略的是:模块间类型引用一旦走通,很容易在开发期掩盖循环依赖问题,直到打包时报错 Cannot resolve dependency 才发现——这时候已经改了三天代码了。

















