You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
8.4 KiB
8.4 KiB
前后端类型自动生成指南
解决前后端类型定义重复的问题
🎯 问题
当前前后端类型定义重复:
# 后端 (server/app/models/project.py)
class ProjectType(IntEnum):
MINE = 1
COLLAB = 2
// 前端 (client/src/types/project.ts)
export type ProjectType = 'mine' | 'collab';
export const PROJECT_TYPE = {
MINE: 'mine',
COLLAB: 'collab',
} as const;
问题:
- ❌ 重复定义,需要手动同步
- ❌ 新增枚举值时容易遗漏
- ❌ 维护成本高
✨ 解决方案:自动生成
方案 A:OpenAPI + TypeScript 生成器 ⭐ 推荐
1. 后端生成 OpenAPI Schema
FastAPI 自动生成 OpenAPI 文档:
# 访问 OpenAPI JSON
http://localhost:8000/openapi.json
2. 安装前端生成工具
cd client
npm install -D openapi-typescript-codegen
# 或
npm install -D @openapitools/openapi-generator-cli
3. 配置生成脚本
package.json:
{
"scripts": {
"generate:types": "openapi-typescript-codegen --input http://localhost:8000/openapi.json --output ./src/types/generated --client axios"
}
}
4. 生成类型
npm run generate:types
生成结果:
// client/src/types/generated/models/ProjectType.ts
export enum ProjectType {
MINE = 'mine',
COLLAB = 'collab',
}
// client/src/types/generated/models/Project.ts
export interface Project {
id: string;
name: string;
type: ProjectType;
status: ProjectStatus;
// ...
}
5. 使用生成的类型
// client/src/pages/WorkspacePage.tsx
import { ProjectType, Project } from '@/types/generated';
if (project.type === ProjectType.MINE) {
// ...
}
优点:
- ✅ 100% 自动同步
- ✅ 类型安全
- ✅ 无需手动维护
- ✅ API 变更自动反映
缺点:
- ⚠️ 需要后端服务运行
- ⚠️ 需要额外构建步骤
方案 B:共享类型定义文件
1. 创建类型定义文件
// shared/types/project.ts
export const PROJECT_TYPE = {
MINE: 'mine',
COLLAB: 'collab',
} as const;
export type ProjectType = typeof PROJECT_TYPE[keyof typeof PROJECT_TYPE];
2. 后端和前端都引用
后端:
# scripts/sync_types.py
import json
# 从 shared/types 读取类型定义
# 验证后端枚举是否一致
前端:
// 直接导入
import { PROJECT_TYPE } from '@shared/types/project';
优点:
- ✅ 单一数据源
- ✅ 手动控制
缺点:
- ❌ 需要额外的验证脚本
- ❌ 跨语言支持复杂
方案 C:代码生成脚本
1. 编写生成脚本
# scripts/generate_frontend_types.py
"""
从后端 Model 自动生成前端 TypeScript 类型
"""
import ast
import re
from pathlib import Path
def extract_enums():
"""提取后端枚举定义"""
model_file = Path("server/app/models/project.py")
with open(model_file) as f:
tree = ast.parse(f.read())
enums = {}
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef):
if any(base.id == 'IntEnum' for base in node.bases if isinstance(base, ast.Name)):
enum_name = node.name
enum_values = {}
for item in node.body:
if isinstance(item, ast.Assign):
name = item.targets[0].id
value = item.value.n
# 查找 to_string 映射
enum_values[name] = value
enums[enum_name] = enum_values
return enums
def generate_typescript(enums):
"""生成 TypeScript 类型定义"""
output = []
for enum_name, values in enums.items():
# 生成类型
type_values = [f"'{v.lower()}'" for v in values.keys()]
output.append(f"export type {enum_name} = {' | '.join(type_values)};")
output.append("")
# 生成常量
const_name = re.sub(r'(?<!^)(?=[A-Z])', '_', enum_name).upper()
output.append(f"export const {const_name} = {{")
for name in values.keys():
output.append(f" {name}: '{name.lower()}',")
output.append("} as const;")
output.append("")
return "\n".join(output)
if __name__ == "__main__":
enums = extract_enums()
ts_code = generate_typescript(enums)
output_file = Path("client/src/types/generated/enums.ts")
output_file.parent.mkdir(exist_ok=True)
output_file.write_text(ts_code)
print(f"✅ Generated TypeScript types: {output_file}")
2. 配置自动运行
// package.json
{
"scripts": {
"predev": "python ../scripts/generate_frontend_types.py",
"prebuild": "python ../scripts/generate_frontend_types.py"
}
}
优点:
- ✅ 自动化
- ✅ 无需外部工具
- ✅ 可定制
缺点:
- ⚠️ 需要维护脚本
- ⚠️ 可能需要处理复杂情况
🎯 推荐方案对比
| 方案 | 自动化程度 | 维护成本 | 类型安全 | 推荐指数 |
|---|---|---|---|---|
| 手动维护 | ❌ 手动 | ⚠️ 高 | ⚠️ 中等 | ⭐⭐ |
| OpenAPI 生成 | ✅ 全自动 | ✅ 低 | ✅ 高 | ⭐⭐⭐⭐⭐ |
| 共享类型文件 | ⚠️ 半自动 | ⚠️ 中等 | ✅ 高 | ⭐⭐⭐ |
| 自定义脚本 | ✅ 自动 | ⚠️ 中等 | ✅ 高 | ⭐⭐⭐⭐ |
🚀 快速实施:OpenAPI 方案
步骤 1:确认 OpenAPI 可用
# 访问 OpenAPI JSON
curl http://localhost:8000/openapi.json | jq '.components.schemas'
步骤 2:安装生成工具
cd client
npm install -D openapi-typescript-codegen
步骤 3:添加生成脚本
// client/package.json
{
"scripts": {
"types:generate": "openapi --input http://localhost:8000/openapi.json --output ./src/types/generated --client axios",
"types:watch": "npm run types:generate -- --watch"
}
}
步骤 4:生成类型
npm run types:generate
步骤 5:使用生成的类型
// 使用生成的类型
import { ProjectType, ProjectStatus } from '@/types/generated';
import type { Project } from '@/types/generated';
// 现在枚举值和类型都是自动生成的!
if (project.type === ProjectType.MINE) {
console.log('个人项目');
}
📋 完整示例
后端(保持不变)
# server/app/models/project.py
class ProjectType(IntEnum):
"""项目类型枚举"""
MINE = 1
COLLAB = 2
@classmethod
def to_string(cls, value: int) -> str:
mapping = {cls.MINE: "mine", cls.COLLAB: "collab"}
return mapping.get(value, "mine")
前端(自动生成)
运行生成:
npm run types:generate
生成的文件 client/src/types/generated/models.ts:
// 🤖 自动生成,请勿手动修改
export enum ProjectType {
MINE = 'mine',
COLLAB = 'collab',
}
export enum ProjectStatus {
ACTIVE = 'active',
ARCHIVED = 'archived',
TRASHED = 'trashed',
SOFT_DELETED = 'soft_deleted',
}
export interface Project {
id: string;
name: string;
type: ProjectType;
status: ProjectStatus;
// ...
}
使用:
import { ProjectType } from '@/types/generated/models';
// ✅ 100% 与后端同步
if (project.type === ProjectType.MINE) {
// ...
}
💡 实用技巧
技巧 1:Git Hook 自动生成
# .husky/pre-commit
#!/bin/sh
cd client && npm run types:generate
git add src/types/generated
技巧 2:CI/CD 检查
# .github/workflows/ci.yml
- name: Check types sync
run: |
npm run types:generate
git diff --exit-code src/types/generated || (echo "Types out of sync!" && exit 1)
技巧 3:VSCode 任务
// .vscode/tasks.json
{
"version": "2.0.0",
"tasks": [
{
"label": "Generate Types",
"type": "npm",
"script": "types:generate",
"problemMatcher": []
}
]
}
🎯 最终建议
小型项目(枚举变化不频繁)
方案:手动维护 + 文档约定
- 简单直接
- 无需额外配置
中大型项目(推荐)⭐
方案:OpenAPI + 自动生成
- 完全自动化
- 100% 同步
- 行业标准
实施路径
- 立即:添加 OpenAPI 生成脚本
- 短期:配置 Git Hook 自动生成
- 长期:集成到 CI/CD
最后更新: 2026-02-06
维护者: Jointo 开发团队