TS 推荐项目结构规范
2026/7/9大约 5 分钟
TS 推荐项目结构规范
面向读者:有 Go 后端经验的开发者。Go 项目有成熟的
cmd/internal/pkg结构共识,前端项目虽然生态不同,但同样需要一套清晰的目录约定。本文会以 Go 结构为参照,帮助理解前端项目的组织逻辑。
一、推荐目录结构模板
my-app/
├── public/ # 静态资源(不经打包,直接托管)
│ ├── favicon.ico
│ └── index.html
│
├── src/ # 源代码
│ ├── types/ # 类型定义(类似 Go types/ 包)
│ │ ├── index.ts # 统一导出
│ │ ├── api.ts # API 请求/响应类型
│ │ └── common.ts # 通用类型
│ │
│ ├── utils/ # 工具函数(类似 Go utils/ 包)
│ │ ├── format.ts # 格式化函数
│ │ └── validation.ts # 校验函数
│ │
│ ├── api/ # API 请求层(类似 Go handler + service)
│ │ ├── request.ts # axios/fetch 实例封装
│ │ ├── user.ts # 用户相关 API
│ │ └── product.ts # 产品相关 API
│ │
│ ├── hooks/ # 自定义 Hooks(Vue composables / React hooks)
│ │ ├── useAuth.ts # 认证逻辑
│ │ └── usePagination.ts # 分页逻辑
│ │
│ ├── components/ # 组件
│ │ ├── common/ # 通用组件(按钮、输入框等)
│ │ │ ├── Button.tsx
│ │ │ └── Input.tsx
│ │ └── business/ # 业务组件
│ │ └── UserCard.tsx
│ │
│ ├── constants/ # 常量(类似 Go const 块)
│ │ ├── index.ts
│ │ └── enums.ts # 枚举值
│ │
│ ├── config/ # 配置(类似 Go config 包)
│ │ └── index.ts
│ │
│ ├── router/ # 路由(如果使用 Vue Router / React Router)
│ │ └── index.ts
│ │
│ ├── store/ # 状态管理(Pinia / Zustand / Redux)
│ │ └── user.ts
│ │
│ ├── App.tsx # 根组件
│ └── main.tsx # 入口文件
│
├── .eslintrc.cjs # ESLint 配置
├── .prettierrc # Prettier 配置
├── tsconfig.json # TS 配置
├── vite.config.ts # 构建配置
├── package.json # 依赖管理
└── pnpm-lock.yaml # 锁定文件与 Go 项目结构对比
| Go 项目 | TS 前端项目 | 说明 |
|---|---|---|
cmd/ | src/main.tsx | 入口点 |
internal/ | src/ | 内部代码 |
pkg/ | src/components/ | 可复用的公共组件/包 |
api/ | src/api/ | API 接口层 |
config/ | src/config/ | 配置 |
go.mod | package.json | 依赖管理 |
cmd/server/main.go | src/App.tsx | 根组件/函数 |
二、Barrel 导出模式 vs Go package 导出
2.1 什么是 Barrel 导出
// src/types/index.ts —— Barrel 文件
export * from './api';
export * from './common';
export type { User, LoginRequest, ApiResponse } from './api';// 外部引用时,一条 import 即可
import { User, LoginRequest } from '@/types';
// 而不是:
import { User } from '@/types/api';
import { LoginRequest } from '@/types/common';2.2 类比 Go
// Go package 导出:每个包一个目录,外部通过包名引用
// package types
// 外部引用:import "myapp/types"
// 使用:types.User| 特性 | TS Barrel 导出 | Go package 导出 |
|---|---|---|
| 导出单位 | 文件级 | 目录(包)级 |
| 引用方式 | import { X } from '@/types' | import "pkg/types" + types.X |
| 命名空间 | 无(手动管理重名) | 包名为隐式命名空间 |
| 自动导出 | 需手动维护 index.ts | 所有首字母大写自动导出 |
推荐实践
// 每个目录下创建一个 index.ts 统一导出
// types/index.ts
export * from './api';
export * from './common';
// components/index.ts
export { Button } from './common/Button';
export { UserCard } from './business/UserCard';三、命名约定
3.1 变量/函数命名
// TS/JS:camelCase(驼峰)
const userName = 'Alice';
function fetchUserData() { /* ... */ }
const getFullName = (first: string, last: string) => `${first} ${last}`;// Go:PascalCase(导出)或 camelCase(未导出)
func GetFullName(first, last string) string // 导出
func getFullName(first, last string) string // 未导出3.2 类型/接口命名
// TS:PascalCase
interface UserProfile {
id: number;
name: string;
}
type ApiResponse<T> = {
code: number;
data: T;
message: string;
};
// 枚举
enum Role {
Admin = 'admin',
User = 'user',
}// Go:PascalCase
type UserProfile struct {
ID int `json:"id"`
Name string `json:"name"`
}3.3 文件命名
| 规范 | TS 前端 | Go 后端 |
|---|---|---|
| 推荐 | kebab-case(连字符) | snake_case |
| 组件文件 | user-card.tsx | 不适用 |
| 工具文件 | format-utils.ts | format_utils.go |
| 类型文件 | api-types.ts | api_types.go |
| 配置文件 | vite.config.ts | 不适用 |
不要混用命名风格
- src/utils/getUserData.ts // camelCase 文件名
- src/utils/FormatUtils.ts // PascalCase 文件名
+ src/utils/get-user-data.ts // ✅ 统一 kebab-case
+ src/utils/format-utils.ts // ✅ 统一 kebab-case四、类型定义:集中 vs 就近
4.1 推荐策略:混合模式
// 1. 全局共享类型 → 集中到 types/
// src/types/api.ts
export interface User {
id: number;
name: string;
email: string;
}
export interface LoginRequest {
username: string;
password: string;
}
// 2. 组件私有类型 → 就近定义
// src/components/business/UserCard.tsx
interface UserCardProps {
user: User;
onEdit: (id: number) => void;
variant?: 'compact' | 'full';
}
export function UserCard({ user, onEdit, variant = 'full' }: UserCardProps) {
return <div>{/* ... */}</div>;
}4.2 类比 Go
// Go 中没有"就近定义结构体"的惯例,通常全部集中在 types/ 或各自包中
// 但 TS 允许函数组件自己声明 props 类型,更接近"自文档化"类型定义决策树
- 该类型被 2 个以上 文件使用 → 放入
types/ - 该类型是 API 响应/请求 类型 → 放入
types/api.ts - 该类型是 组件 Props → 就近定义在组件文件中
- 该类型是 枚举常量 → 放入
constants/enums.ts
五、反模式与最佳实践
Bad:扁平目录
src/
├── utils.ts
├── helper.ts
├── api.ts
├── types.ts
├── constants.ts
├── components.ts
├── hooks.ts
└── main.tsx注意
扁平目录在项目初期看起来"简洁",但一旦文件超过 10 个就会变得难以维护——类比 Go 项目把所有代码放在一个 package main 中。
Best:按功能模块分包
src/
├── user/
│ ├── api.ts # 用户相关 API
│ ├── types.ts # 用户相关类型
│ ├── hooks.ts # 用户相关 hooks
│ ├── UserList.tsx # 用户列表组件
│ └── UserCard.tsx # 用户卡片组件
├── product/
│ ├── api.ts
│ ├── types.ts
│ ├── hooks.ts
│ ├── ProductList.tsx
│ └── ProductCard.tsx
├── shared/ # 共享内容
│ ├── components/
│ ├── hooks/
│ └── utils/
└── types/ # 全局类型为什么推荐功能分包?
- 高内聚:用户相关的 API、类型、组件放在一起,修改时只需关注一个目录
- 低耦合:不同功能之间通过
types/共享类型,减少循环依赖 - 可删除:某个功能不再需要时,直接删除整个目录
- 类比 Go 中每个微服务一个目录的做法
六、路径别名配置
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}// 使用路径别名,避免深层相对路径
// 不推荐
import { User } from '../../../types/api';
// 推荐
import { User } from '@/types/api';类比 Go
Go 中通过 go.mod 的 module 定义项目根路径,引用时使用完整包路径:
// go.mod: module github.com/myapp
import "github.com/myapp/internal/types"TS 的 @/ 别名相当于为 src/ 目录创建了一个"包根路径",作用类似。
总结
| 规范 | TS 前端 | Go 后端 |
|---|---|---|
| 目录结构 | src/types/utils/api/hooks/components | cmd/internal/pkg |
| 导出模式 | Barrel (index.ts) | package-level export |
| 变量命名 | camelCase | PascalCase(导出)/camelCase |
| 文件命名 | kebab-case | snake_case |
| 类型定义 | 集中 + 就近混合 | 包级集中定义 |
| 路径引用 | @/ 别名 | module path + 包路径 |
| 最佳实践 | 按功能模块分包 | 按职责分层 |
下一站:tsconfig 配置详解,学习如何配置 TypeScript 编译器。