人人都会AI编程

20.4 目录结构与模块分层规范

更新时间:2026-07-10

良好的目录结构是项目可维护性的基石。随着项目规模增长,没有规范的文件组织会迅速导致“找不到文件”、“循环依赖”、“职责不清”等问题。本节提供一套经过大量项目验证的分层规范,适用于中大型 React 应用。

核心原则

1. 关注点分离
将不同职责的代码放入不同层级,比如界面展示、业务逻辑、数据请求、通用工具应明确分开。

2. 高内聚,低耦合
同一功能模块的相关文件尽量放在一起,减少跨模块的间接依赖。

3. 易于发现
目录命名和文件命名要有意义,让开发者能根据需求快速定位到对应模块。

4. 渐进式复杂
初期不用追求完美目录,但要有意识地随着模块增多逐步拆分。一个小项目不需要一开始就搞微前端级别的分层。

推荐的分层架构

一个典型的中大型 React 项目可划分为以下五个层次:

src/
├── assets/          # 静态资源(图片、字体、全局样式)
├── components/      # 通用 UI 组件(无业务逻辑)
├── features/        # 业务功能模块(按业务域拆分)
├── hooks/           # 全局共享的自定义 Hooks
├── services/        # API 请求与数据处理
├── store/           # 全局状态管理
├── types/           # TypeScript 类型定义
├── utils/           # 纯工具函数
├── layouts/         # 页面布局组件
├── pages/           # 路由页面入口
├── routes/          # 路由配置
└── App.tsx          # 根组件

这种分层将“通用能力”与“业务逻辑”分开,方便复用与维护。


各目录详细说明

assets/ — 静态资源

存放图片、字体、全局 CSS/SCSS 文件、第三方样式等。如果使用 CSS Modules 或 CSS-in-JS,样式通常会内联到组件目录中,那么 assets/ 主要用于全局资源和字体。

assets/
├── images/
│   ├── logo.svg
│   └── avatar-default.png
├── fonts/
└── styles/
    └── global.css          # CSS 变量、重置样式

components/ — 通用 UI 组件

纯展示型组件,不包含具体业务逻辑,通过 Props 获取数据和回调。典型例子:ButtonInputModalTableCardEmptyState 等。

每个组件一个文件夹,包含组件文件、样式文件和测试文件:

components/
├── Button/
│   ├── Button.tsx
│   ├── Button.module.css
│   └── Button.test.tsx
├── Modal/
│   ├── Modal.tsx
│   ├── Modal.module.css
│   └── index.ts            # 统一导出
└── index.ts                # 统一入口,方便引用

命名规范:组件文件名与组件名保持一致,使用 PascalCase(大驼峰)。

features/ — 业务功能模块

这是代码的核心部分,按业务领域拆分。每个 feature 包含自己的组件、Hooks、类型、请求等。避免一个 feature 直接引用另一个 feature 的内部文件(应通过公共接口或 components/ 通信),防止耦合。

features/
├── auth/
│   ├── components/         # 该业务特有的组件
│   │   ├── LoginForm.tsx
│   │   └── RegisterForm.tsx
│   ├── hooks/              # 业务 Hooks
│   │   └── useAuth.ts
│   ├── services/           # 该业务的 API
│   │   └── authService.ts
│   └── types.ts            # 该业务的类型定义
├── product/
│   ├── components/
│   │   ├── ProductList.tsx
│   │   └── ProductCard.tsx
│   ├── hooks/
│   │   └── useProducts.ts
│   └── services/
│       └── productService.ts
└── user/
    ├── components/
    │   ├── UserProfile.tsx
    │   └── AvatarUpload.tsx
    ├── hooks/
    │   └── useUser.ts
    └── services/
        └── userService.ts

这样的好处是:当你需要修改“产品”相关功能时,只需在 features/product/ 内工作,不用担心影响认证或用户模块。

hooks/ — 全局共享 Hooks

存放跨多个 feature 使用的自定义 Hooks,如 useDebounceuseLocalStorageuseMediaQuery。如果是某个业务独有的 Hooks,应该放在对应 feature 的 hooks/ 下。

hooks/
├── useDebounce.ts
├── useLocalStorage.ts
└── useDocumentTitle.ts

services/ — API 服务层

封装所有 HTTP 请求,通常基于 Axios 或 fetch。包含请求的基础配置(拦截器、错误处理、Token 刷新)和对各业务接口的封装函数。

services/
├── http.ts                # Axios 实例配置
├── authService.ts         # 与认证相关的 API
├── productService.ts      # 与产品相关的 API
└── userService.ts         # 用户相关 API

如果使用 TanStack Query,也可以将 services/ 与 Query Hooks 结合在 features/ 内,视团队偏好而定。

store/ — 全局状态管理

如果使用 Redux Toolkit,这里存放 store 配置、各 slice 文件;如果使用 Zustand,则存放各 store 文件。同样,只放真正全局共享的状态,避免过度使用全局状态。

store/
├── index.ts              # store 实例
├── slices/
│   ├── authSlice.ts
│   └── cartSlice.ts
└── hooks.ts              # 自定义的 useAppDispatch/useAppSelector

types/ — 全局类型定义

全局通用的 TypeScript 类型声明,如 API 响应结构、通用泛型等。业务相关的类型定义在对应 feature 内。

types/
├── api.ts                # API 通用响应类型
├── common.ts             # 通用类型,如 Pagination
└── global.d.ts           # 全局声明文件

utils/ — 工具函数

纯函数,不依赖 React,不包含 JSX。例如:日期格式化、树结构转换、权限检查、数据过滤等。通常可通过单元测试独立验证。

utils/
├── formatDate.ts
├── treeUtils.ts
└── permission.ts

layouts/ — 布局组件

不同页面的布局框架,如带侧边栏的后台布局、无导航的登录页布局等。

layouts/
├── MainLayout.tsx        # 主要布局:Header + Sidebar + Content
├── AuthLayout.tsx        # 登录/注册等简单居中布局
└── BlankLayout.tsx       # 空白布局

pages/ — 路由页面

每个路由对应一个页面组件,通常是“薄组件”:只负责组合 features/ 中的业务组件和 layouts/,本身不做复杂业务逻辑。

pages/
├── Dashboard/
│   ├── Dashboard.tsx
│   └── index.ts
├── Login/
│   ├── Login.tsx
│   └── index.ts
└── ProductList/
    ├── ProductList.tsx
    └── index.ts

如果项目使用 Next.js(App Router),pages/ 会被 app/ 目录代替,但思想一致。

routes/ — 路由配置

集中定义应用的路由树、权限、懒加载等。通常使用 React Router 的配置方式。

routes/
├── index.tsx              # 路由配置数组
├── ProtectedRoute.tsx     # 路由守卫组件
└── routePaths.ts          # 路径常量

文件命名规范

| 类型 | 规范 | 示例 |
|------|------|------|
| 组件文件 | PascalCase | UserProfile.tsx |
| 组件样式 | 同组件名 + .module.css | UserProfile.module.css |
| Hooks 文件 | camelCase,以 use 开头 | useAuth.ts |
| 工具函数 | camelCase | formatDate.ts |
| 类型文件 | camelCase 或按模块命名 | user.ts, product.ts |
| 目录 | 小写 + 连字符 或 PascalCase(看团队) | user-profile/UserProfile/ |
| 常量文件 | camelCase 或 UPPER_CASE | apiConstants.ts |

原则:确保团队成员能通过文件名猜出文件内容,避免 index.tsx 滥用导致编辑器标签页全是 index


不同规模项目的参考剪裁

小型项目(< 10 个页面)
可能不需要 features/ 分层,直接用 components/ + pages/ + hooks/ 即可,避免过度设计。

中型项目(10-30 页面)
采用完整分层:components/features/layouts/ 结合。

大型项目(多业务域)
可以考虑 Monorepo 结构,每个业务域独立成包(packages),共享 components/utils/


模块导入与导出规范

每个目录应有 index.ts 桶文件 统一导出,但不要过度封装。

// components/Button/index.ts
export { Button } from './Button';

避免深层相对路径引用。配置 TypeScript 路径别名:

// tsconfig.json
{
  "compilerOptions": {
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@features/*": ["src/features/*"],
      "@utils/*": ["src/utils/*"]
    }
  }
}

使用时:

import { Button } from '@components/Button';
import { useAuth } from '@features/auth/hooks/useAuth';

这样看起来清晰,方便重构。


常见反模式

  1. 将业务逻辑写在页面组件中pages/ 应当尽可能是组合者,不要包含复杂的状态管理或 API 请求。
  2. 跨 feature 直接引用内部文件:导致隐式依赖和紧耦合,应通过抽出公共组件或 Hooks 解决。
  3. components/ 里包含业务代码:通用组件不能依赖具体业务数据结构。
  4. 样式与组件分离过远:组件所需的样式应放在同级目录,而不是在单独 styles/ 目录中按页面划分。
  5. 滥用全局状态:能用 Props 或局部状态解决的,不要推入全局 store。

总结

好的目录结构是一份活文档,新成员根据目录即可理解项目的大致架构。遵循“按功能分层、按领域分块、就近原则”的规范,可以让项目在从零到百万行代码的过程中始终保持清晰。切记,规范不是教条,要结合实际团队规模和项目复杂度灵活调整,但一以贯之才是关键。