本文目录导读:
这是一个非常核心且复杂的问题,全栈框架的模块化拆分没有“银弹”,它依赖于项目规模、团队结构、技术栈(如Next.js, Nuxt, Remix, Django/React等)以及业务领域。
模块化拆分可以从两个维度进行:垂直分层(技术视角) 和水平分域(业务视角)。
以下是一套适用于现代全栈(特别是前后端融合框架如Next.js、Nuxt、SvelteKit或T3 Stack)的通用拆分方法论。
核心原则:关注点分离 + 高内聚低耦合
你的代码应该像乐高积木,而非意大利面条。
水平分域:按“业务模块”拆分(最推荐)
这是最符合产品逻辑的拆分方式,想象你的应用有“用户中心”、“商品管理”、“订单系统”三大块。
目录结构示例(以Next.js App Router为例):
my-app/ ├── src/ │ ├── modules/ # 核心业务模块(水平拆分) │ │ ├── auth/ # 1. 认证模块 │ │ │ ├── components/ # 仅属于auth的UI组件 │ │ │ ├── api/ # 仅属于auth的API路由 (Next.js Route Handlers) │ │ │ ├── hooks/ # 仅属于auth的React hooks │ │ │ ├── lib/ # 仅属于auth的纯函数、验证逻辑 │ │ │ └── types/ # 仅属于auth的TypeScript类型 │ │ │ │ │ ├── products/ # 2. 商品模块 │ │ │ ├── components/ │ │ │ ├── api/ │ │ │ ├── hooks/ │ │ │ └── types/ │ │ │ │ │ └── orders/ # 3. 订单模块 │ │ ├── components/ │ │ ├── api/ │ │ └── types/ │ │ │ ├── shared/ # 全局共享层(垂直分层) │ │ ├── ui/ # 通用UI组件 (Button, Card, Modal) │ │ ├── lib/ # 通用工具函数 (date, format, API client) │ │ ├── config/ # 环境变量、常量 │ │ └── middleware/ # 全局中间件 (如权限校验) │ │ │ └── app/ # Next.js App Router 入口 (页面路由) │ ├── layout.tsx │ ├── page.tsx │ ├── auth/ # 页面路由指向 auth 模块 │ └── products/ # 页面路由指向 products 模块
这样做的好处:
- 高内聚:修改“订单”逻辑,只需改动
modules/orders文件夹,不影响“商品”。 - 可裁剪:如果后期要拆成微前端,可以直接将
modules/orders整个打包成独立应用。 - 职责清晰:新人入职只需阅读相关
modules即可理解业务。
垂直分层:按“技术层次”拆分(通用层)
任何模块内部,都应遵循清晰的技术分层。
| 层级 | 职责 | 严禁做的事 |
|---|---|---|
| Presenter (UI) | 页面组件、布局、交互逻辑 | 直接读写数据库、调用后端API原始数据 |
| Hooks / State | 管理组件状态、副作用处理(React Query, Zustand) | 包含复杂业务逻辑(应调用下方服务) |
| Services / API | 发起网络请求、调用外部API、处理数据格式化 | 直接操作DOM或React状态 |
| Database (ORM) | Prisma Schema, Drizzle, Mongoose Models | 暴露给UI层直接的数据库操作 |
| Types / Schema | Zod / Zodios / TypeScript类型定义,定义契约 | 在UI层硬编码类型(保持共享) |
在模块内部的应用:
// modules/orders/api/create-order.ts (API层)
export async function createOrder(data: CreateOrderInput) {
// 1. 验证
// 2. 调用Prisma/SQL
// 3. 调用支付网关
// 4. 返回结果
}
// modules/orders/hooks/use-create-order.ts (Hooks层)
export function useCreateOrder() {
const mutation = useMutation({ mutationFn: createOrder });
// ... react-query 逻辑
return { ...mutation };
}
// modules/orders/components/order-form.tsx (UI层)
function OrderForm() {
const { mutate } = useCreateOrder();
// 只负责渲染表单并调用 mutate
}
关键拆分策略(实操技巧)
共享内核(Shared Kernel)
把所有模块都可能用的、极难变更的东西收拢到 shared 或 lib 文件夹。
- 禁止:把业务相关的API、Schema放在shared里。
createUser函数不应该在shared里,它应该属于modules/auth。
数据流(Data Flow)契约化
使用 Zod / TypeScript 定义模块间的数据接口。
// modules/auth/types/user.ts
export interface User {
id: string;
name: string;
email: string;
}
// modules/orders/types/order.ts
import type { User } from '@/modules/auth/types';
export interface Order {
id: string;
user: User; // 引用其他模块的类型,但保持明确的导入关系
total: number;
}
后端与前端边界(BFF - Backend For Frontend)
全栈框架(Next.js API Routes, Nuxt Server)天然充当了BFF层。
- 不要:在前端组件里直接
fetch('https://external.com/api')。 - 要:通过框架自身API路由做代理或聚合,实现后端防护(隐藏秘钥、数据裁剪)。
实战案例:一个“电商后台”的拆分
假设使用 Next.js + Prisma + tRPC(类型安全RPC框架)。
src/ ├── modules/ │ ├── auth/ # 登录、注册、权限 │ │ ├── components/ (LoginForm.tsx) │ │ ├── server/ (auth.router.ts - tRPC路由) │ │ └── schemas/ (zod schemas for validation) │ │ │ ├── inventory/ # 库存管理(独立模块) │ │ ├── components/ │ │ ├── server/ (inventory.router.ts) │ │ └── schemas/ (inventory-schema.ts) │ │ │ └── analytics/ # 数据分析(后期大模块,甚至可独立部署) │ └── ... │ ├── shared/ │ ├── ui/ # shadcn/ui 组件 │ ├── lib/ # db.ts (Prisma Client初始化), api-client.ts │ └── types/ # 全局枚举、常量 │ ├── app/ # 页面路由(根据业务模块组织文件夹) │ ├── (auth)/ # 路由组,用于登录/注册页面布局 │ ├── (dashboard)/ │ │ ├── inventory/ │ │ └── orders/ │ └── api/ # tRPC API路由 或 Next.js Route Handlers │ └── trpc/ │ └── [trpc].ts
你应该按哪个拆?
- 小项目/原型(<5个页面):先按垂直分层(
/components,/hooks,/api),模块化会带来额外心智负担。 - 中等项目(5-20个页面,2-5人团队):强烈建议使用
src/modules/(水平),这是最平衡的选择,业务逻辑清晰,团队协作高效。 - 大型项目(微前端/多团队):考虑Monorepo (Turborepo / Nx) + 独立包(Packages)。
packages/ ├── ui/ # 共享UI组件库 ├── config/ # 共享eslint, tsconfig ├── auth/ # 独立发布包(模块独立) └── order/ # 独立发布包 apps/ ├── admin/ # 后台(引入 auth, order包) └── store/ # 前台(引入 auth 包)
一句话建议:
先以 modules/ 按业务拆分,再在每个模块内部做垂直分层,比单层的 components/ 和 utils/ 要好维护得多。