前端项目目录结构
这篇文章是描述如何在前端项目里组织目录结构。
目录
.
├── docs # 项目文档,同时可以给人和给 AI 看
├── static # 静态资源文件夹,这个目录下的文件,通常不会被做任何修改,直接 serve 使用
├── src
│ ├── app.config.ts # 应用全局配置
│ ├── app.{scss|css|less} # 应用全局样式入口
│ ├── app.tsx # 应用主组件
│ ├── main.ts # 应用主入口
│ ├── assets # 静态资源文件夹,这个目录下的文件通常会被打包工具打包进 js 文件里,或者由打包工具输出到 static
│ ├── packs # 可重复使用的功能包(和 modules 类似,但不同的是可以在多个模块中跨模块使用)
│ ├── components # 全局通用组件
│ ├── index.html # HTML 模板
│ ├── layouts # 页面布局组件
│ ├── stores # 全局跨组件状态库 (例如 Jotai 、 Pinia 等)
│ ├── pages # 页面路由组件(目录结构通常对应应用的页面路由路径)
│ ├── styles # 全局样式文件
│ ├── wrappers # 全局 Context Provider 包装器
│ ├── modules # 模块
│ │ ├── <Module1> # 模块 1
│ │ ├── <Module2> # 模块 2
│ │ └── <Module3> # 模块 3
│ │ ├── packs # 可重复使用的功能包,仅限当前模块使用(和 modules 类似,但不同的是可以在多个模块中跨模块使用)
│ │ ├── components # 通用组件,仅限当前模块使用
│ │ ├── views # 视图组件,仅限当前模块使用
│ │ ├── modules # 当前模块的子模块
│ │ │ ├── <SubModule1> # 子模块 1
│ │ │ └── <SubModule2> # 子模块 2
│ │ └── stores # 跨组件状态库,仅限当前模块使用
│ └────── <Module4> # 模块 4
└── typings # 全局 TypeScript 类型定义对于 pages、components 和 views 文件夹里的组件类型,可以参考 区分组件类型 这篇文档
命名规范
本项目遵循特定的文件和目录命名规范:
- kebab-case(短横线命名法):用于固定目录和页面组件
- 示例:
pages、styles、parts、components、views
- 示例:
- PascalCase(帕斯卡命名法,大驼峰命名法):用于业务相关的目录、文件和组件
- 示例:模块名称、通用组件文件、视图组件文件
模块下的 PascalCase 命名的文件,应该加上前序和当前模块名作为前缀。
示例
- 页面组件:
user-profile.tsx、home-page.tsx - 通用/视图组件:
DatePicker.tsx、UserProfileView.tsx - 模块目录:
UserManagement、TaskScheduler
模块结构
本文档解释了本项目中模块的组织方式,以及组件归属与作用域的相关规则。
应用的功能模块统一放在 parts 目录下。每个模块的结构类似于根目录下的 src,但其作用范围仅限本模块。
除了 parts 也可以使用 modules 作为模块文件夹的名字。
典型模块结构
parts/
└── <ModuleName>/
├── components/ # 模块内的通用组件
├── views/ # 模块内的视图组件
├── stores/ # 模块专属的状态管理
└── parts/ # 子模块
├── <SubModule1>/
└── <SubModule2>/作用域规则
组件作用域
各模块下的 components、views 和 stores 必须遵守严格的作用域规则:
模块专属组件
在某个模块中定义的组件禁止被模块以外的地方导入。
示例:
parts/
├── UserManagement/
│ └── components/
│ └── UserAvatar.tsx # 只能在 UserManagement 模块内使用
└── Dashboard/
└── views/
└── DashboardView.tsx # ❌ 不应导入 UserAvatar.tsx全局组件
需要被多个模块共同使用的组件,应放在 src/components/ 下。
示例:
src/
├── components/
│ └── Avatar.tsx # ✓ 可全局使用
└── parts/
├── UserManagement/
│ └── views/
│ └── UserView.tsx # ✓ 可以导入 Avatar.tsx
└── Dashboard/
└── views/
└── DashboardView.tsx # ✓ 可以导入 Avatar.tsxStore(状态)作用域
状态管理(Jotai atoms)遵循与组件相同的作用域规则:
parts/<Module>/stores/下的 store 仅限于本模块内部使用src/stores/下的全局 store 可供整个项目任意地方调用
组件归属策略
最近原则
新建组件时,原则上应放置在最近且最局部的位置,即距离其实际被引入(import)处最近的目录。
示例:
如果 ComponentA 只在 parts/ModuleX/views/ViewY.tsx 中使用:
parts/
└── ModuleX/
├── components/
│ └── ComponentA.tsx # ✓ 距离使用点最近
└── views/
└── ViewY.tsx # import ComponentA作用域提升(Scope Promotion)
所谓作用域提升,即当组件需要更广泛的复用时,将其从局部目录提升到上一级模块或全局目录。
何时进行提升
当出现以下任一情况时,应考虑提升组件作用域:
- 需要在父模块使用该组件
- 需要在同级其它子模块间共享
- 需要在全局(多模块)范围使用
如何进行提升
场景1:从子模块提升到父模块
- 提升前:
parts/
└── UserManagement/
└── parts/
└── UserProfile/
└── components/
└── ProfileCard.tsx # 仅 UserProfile 使用- 提升后(需要在 UserManagement 范围使用时):
parts/
└── UserManagement/
├── components/
│ └── ProfileCard.tsx # ✓ 提升至父模块
└── parts/
└── UserProfile/场景2:从模块提升为全局组件
- 提升前:
parts/
└── UserManagement/
└── components/
└── StatusBadge.tsx # 仅本模块使用- 提升后(需跨模块复用时):
src/
├── components/
│ └── StatusBadge.tsx # ✓ 提升为全局
└── parts/
├── UserManagement/
├── TaskManagement/
└── Dashboard/最佳实践
1. 局部优先,按需提升
- 优先将组件放在最具体、最近的位置
- 只有真的需要时再进行提升
- 不要提前将所有东西都放在全局目录(避免过早优化)
2. 保持模块独立
- 各功能模块尽量独立、内聚
- 避免兄弟模块间的强耦合
- 如需共用代码可采取
- 建立父级公共模块
- 全局提升
- 提取到新的独立模块
3. 记录依赖与可复用性
组件提升时,请务必注意:
- 更新组件注释和使用说明文档
- 检查依赖是否限定于原模块
- 确认组件在新作用域下具备真正的通用性
4. all file types 共用原则
上述作用域与提升原则,适用于以下文件类型:
- 通用组件和视图组件
- 状态管理 store(Jotai atoms)
- 工具函数和通用方法
- 类型定义(非全局类型)
- 样式文件(非全局样式)
示例工作流
新增特性时
- Dashboard 另一个视图也用到这个按钮:
- ✓ 已在组件目录,无需调整
- 其它模块视图需要用该按钮:
- ✗ 当前组件目录作用域过窄
- ✓ 提升到
src/components/WidgetButton.tsx全局目录
- 其它模块需要同样的 state:
- ✗ 当前 store 作用域过窄
- ✓ 提升到
src/stores/widgetStore.ts
为 widget 添加状态管理:
parts/Dashboard/stores/widgetStore.ts需要一个 Widget 通用按钮组件:
parts/Dashboard/components/WidgetButton.tsx在最近范围内新建组件:
parts/Dashboard/views/WidgetView.tsx反模式示例
❌ 跨模块导入
// 错误!!不应跨模块引用私有组件
import { UserCard } from '../../UserManagement/components/UserCard';❌ 过早全局化
// 仅被某一模块使用的组件 不应放在 src/components/
src/components/VerySpecificDashboardWidget.tsx // ❌ 应放在 parts/Dashboard/❌ 深层路径引用
// 示例:路径已经很深时,应考虑提升
import { Component } from '../../../parts/Module/SubModule/components/Component'; // ❌ 请考虑作用域提升总结
- 组件应放在离使用场景最近的位置
- 需要更广泛复用时提升作用域
- 保持模块独立与内聚,防止模块间耦合
- 所有文件类型均应遵循上述规则
- 开发时由局部到全局,逐步推广,避免一开始就全局化
如有特殊场景,可结合项目实际适度调整,但整体遵循“最近原则 —> 按需提升 —> 保持独立”的思想。
Member discussion