# 代码规范 > **版本:** v1.0 > **创建日期:** 2025-10-10 > **适用范围:** 前端(React/TypeScript)+ 后端(Node.js/TypeScript) --- ## 📋 目录 1. [通用规范](#通用规范) 2. [TypeScript规范](#typescript规范) 3. [React规范](#react规范) 4. [Node.js后端规范](#nodejs后端规范) 5. [命名规范](#命名规范) 6. [注释规范](#注释规范) 7. [Git提交规范](#git提交规范) --- ## 通用规范 ### 代码风格 - ✅ 使用ESLint和Prettier统一代码风格 - ✅ 缩进:2个空格 - ✅ 字符串:优先使用单引号 `'` - ✅ 行尾:不加分号(除非必要) - ✅ 单行最大长度:100字符 - ✅ 使用尾随逗号(对象、数组) ### 文件组织 - ✅ 一个文件一个组件/类 - ✅ 相关文件放在同一目录 - ✅ 使用barrel exports(index.ts) - ✅ 测试文件与源文件同目录 ``` src/ ├── components/ │ ├── Button/ │ │ ├── Button.tsx │ │ ├── Button.test.tsx │ │ ├── Button.styles.ts │ │ └── index.ts # export { Button } from './Button' ``` ### 代码注释 - ✅ 复杂逻辑必须注释 - ✅ 公共API必须注释 - ✅ 避免无用注释 - ✅ 使用JSDoc格式 --- ## TypeScript规范 ### 类型定义 **✅ 推荐:** ```typescript // 使用interface定义对象结构 interface User { id: string email: string name?: string } // 使用type定义联合类型 type Status = 'active' | 'inactive' | 'suspended' // 使用enum定义常量集合 enum UserRole { USER = 'user', ADMIN = 'admin', } ``` **❌ 避免:** ```typescript // 不要使用any function process(data: any) { // ❌ // ... } // 应该明确类型 function process(data: ProcessData) { // ✅ // ... } ``` ### 类型导入导出 ```typescript // types.ts export interface Project { id: string name: string description: string } export type ProjectStatus = 'active' | 'archived' // project.service.ts import type { Project, ProjectStatus } from './types' ``` ### 严格模式 ```json // tsconfig.json { "compilerOptions": { "strict": true, "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true } } ``` --- ## React规范 ### 组件定义 **✅ 推荐:函数组件 + Hooks** ```tsx import { useState } from 'react' interface ButtonProps { label: string onClick: () => void variant?: 'primary' | 'secondary' disabled?: boolean } export function Button({ label, onClick, variant = 'primary', disabled = false }: ButtonProps) { const [isLoading, setIsLoading] = useState(false) const handleClick = async () => { setIsLoading(true) try { await onClick() } finally { setIsLoading(false) } } return ( ) } ``` **❌ 避免:类组件** ```tsx // 除非有特殊需求,否则不使用类组件 class Button extends React.Component { ... } // ❌ ``` ### Hooks规范 **✅ 推荐:自定义Hooks** ```typescript // useProjects.ts import { useState, useEffect } from 'react' import { projectService } from '@/services' import type { Project } from '@/types' export function useProjects() { const [projects, setProjects] = useState([]) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) useEffect(() => { loadProjects() }, []) const loadProjects = async () => { setLoading(true) setError(null) try { const data = await projectService.getProjects() setProjects(data) } catch (err) { setError(err as Error) } finally { setLoading(false) } } return { projects, loading, error, reload: loadProjects } } // 使用 function ProjectList() { const { projects, loading, error } = useProjects() if (loading) return if (error) return return (
    {projects.map(project => (
  • {project.name}
  • ))}
) } ``` ### 组件组织 ```tsx // ✅ 推荐的组件结构 import { useState, useEffect, useMemo, useCallback } from 'react' import { useNavigate } from 'react-router-dom' import { SomeComponent } from '@/components' import { useCustomHook } from '@/hooks' import type { SomeType } from '@/types' interface ComponentProps { // props定义 } export function Component({ prop1, prop2 }: ComponentProps) { // 1. Hooks const navigate = useNavigate() const [state, setState] = useState() const { data } = useCustomHook() // 2. 派生状态(useMemo) const computedValue = useMemo(() => { return heavyComputation(data) }, [data]) // 3. 事件处理(useCallback) const handleClick = useCallback(() => { // 处理逻辑 }, []) // 4. Effects useEffect(() => { // 副作用 }, []) // 5. 早期返回(Loading/Error) if (!data) return // 6. 渲染 return (
{/* JSX */}
) } ``` ### 条件渲染 **✅ 推荐:** ```tsx // 简单条件:使用 && {isLoggedIn && } // if-else:使用三元运算符 {isLoggedIn ? : } // 多条件:提取为函数或组件 function renderContent() { if (loading) return if (error) return if (data.length === 0) return return } return
{renderContent()}
``` **❌ 避免:** ```tsx // 避免复杂的嵌套三元运算符 {condition1 ? ( condition2 ? : ) : ( condition3 ? : )} // ❌ 难以理解 ``` --- ## Node.js后端规范 ### 文件组织 ``` backend/src/ ├── routes/ # 路由定义 │ ├── auth.routes.ts │ └── project.routes.ts ├── services/ # 业务逻辑 │ ├── auth.service.ts │ └── project.service.ts ├── controllers/ # 控制器(可选) ├── models/ # Prisma模型 ├── utils/ # 工具函数 ├── config/ # 配置加载 ├── types/ # 类型定义 └── server.ts # 入口文件 ``` ### 路由定义 ```typescript // routes/project.routes.ts import { FastifyInstance } from 'fastify' import { projectService } from '../services/project.service' import { authMiddleware } from '../middleware/auth' export async function projectRoutes(server: FastifyInstance) { // 获取项目列表 server.get( '/api/v1/projects', { preHandler: [authMiddleware], schema: { querystring: { type: 'object', properties: { page: { type: 'number' }, pageSize: { type: 'number' }, }, }, }, }, async (request, reply) => { const { page = 1, pageSize = 20 } = request.query as any const userId = request.user.id const result = await projectService.getProjects(userId, { page, pageSize, }) return reply.send({ success: true, data: result, }) } ) // 创建项目 server.post( '/api/v1/projects', { preHandler: [authMiddleware], schema: { body: { type: 'object', required: ['name', 'description'], properties: { name: { type: 'string', minLength: 1, maxLength: 200 }, description: { type: 'string', minLength: 1 }, }, }, }, }, async (request, reply) => { const userId = request.user.id const data = request.body as CreateProjectDto const project = await projectService.createProject(userId, data) return reply.code(201).send({ success: true, data: project, }) } ) } ``` ### Service层 ```typescript // services/project.service.ts import { prisma } from '../lib/prisma' import type { CreateProjectDto, UpdateProjectDto } from '../types' export class ProjectService { /** * 获取用户的项目列表 */ async getProjects(userId: string, options: PaginationOptions) { const { page, pageSize } = options const [items, total] = await Promise.all([ prisma.project.findMany({ where: { userId }, skip: (page - 1) * pageSize, take: pageSize, orderBy: { createdAt: 'desc' }, }), prisma.project.count({ where: { userId } }), ]) return { items, pagination: { page, pageSize, total, totalPages: Math.ceil(total / pageSize), hasNext: page * pageSize < total, hasPrev: page > 1, }, } } /** * 创建项目 */ async createProject(userId: string, data: CreateProjectDto) { return prisma.project.create({ data: { userId, name: data.name, description: data.description, }, }) } /** * 更新项目 */ async updateProject( userId: string, projectId: string, data: UpdateProjectDto ) { // 验证权限 const project = await prisma.project.findFirst({ where: { id: projectId, userId }, }) if (!project) { throw new Error('Project not found or unauthorized') } return prisma.project.update({ where: { id: projectId }, data, }) } /** * 删除项目 */ async deleteProject(userId: string, projectId: string) { // 验证权限 const project = await prisma.project.findFirst({ where: { id: projectId, userId }, }) if (!project) { throw new Error('Project not found or unauthorized') } await prisma.project.delete({ where: { id: projectId }, }) } } export const projectService = new ProjectService() ``` ### 错误处理 ```typescript // utils/errors.ts export class AppError extends Error { constructor( public code: string, public message: string, public statusCode: number = 400, public details?: any ) { super(message) this.name = 'AppError' } } export class ValidationError extends AppError { constructor(message: string, details?: any) { super('VALIDATION_ERROR', message, 422, details) } } export class UnauthorizedError extends AppError { constructor(message: string = 'Unauthorized') { super('UNAUTHORIZED', message, 401) } } export class NotFoundError extends AppError { constructor(resource: string) { super('NOT_FOUND', `${resource} not found`, 404) } } // 使用 async function getProject(id: string) { const project = await prisma.project.findUnique({ where: { id } }) if (!project) { throw new NotFoundError('Project') } return project } ``` ### 错误处理中间件 ```typescript // middleware/error-handler.ts import { FastifyError, FastifyReply, FastifyRequest } from 'fastify' import { AppError } from '../utils/errors' export async function errorHandler( error: FastifyError | AppError, request: FastifyRequest, reply: FastifyReply ) { // 记录错误 request.log.error(error) // 自定义错误 if (error instanceof AppError) { return reply.code(error.statusCode).send({ success: false, error: { code: error.code, message: error.message, details: error.details, }, timestamp: new Date().toISOString(), }) } // Prisma错误 if (error.name === 'PrismaClientKnownRequestError') { // 处理Prisma特定错误 return reply.code(400).send({ success: false, error: { code: 'DATABASE_ERROR', message: 'Database operation failed', }, timestamp: new Date().toISOString(), }) } // 默认错误 return reply.code(500).send({ success: false, error: { code: 'INTERNAL_ERROR', message: 'Internal server error', }, timestamp: new Date().toISOString(), }) } ``` --- ## 命名规范 ### 文件命名 | 类型 | 命名方式 | 示例 | |------|---------|------| | React组件 | PascalCase | `Button.tsx`, `ProjectList.tsx` | | Hooks | camelCase + use前缀 | `useProjects.ts`, `useAuth.ts` | | 工具函数 | camelCase | `formatDate.ts`, `api.ts` | | 类型定义 | camelCase + .types | `user.types.ts`, `api.types.ts` | | 常量 | camelCase + .constants | `routes.constants.ts` | | 测试文件 | 同源文件 + .test | `Button.test.tsx` | ### 变量命名 ```typescript // ✅ 推荐 const userName = 'John' // camelCase const USER_ROLE = 'admin' // 常量用UPPER_SNAKE_CASE const isLoading = false // 布尔值用is/has/can前缀 const hasPermission = true const canEdit = false // ❌ 避免 const user_name = 'John' // 不用snake_case const loading = false // 布尔值缺少is前缀 const x = 10 // 无意义的变量名 ``` ### 函数命名 ```typescript // ✅ 推荐 function getUser() { } // get: 获取数据 function fetchProjects() { } // fetch: 异步获取 function createProject() { } // create: 创建 function updateProject() { } // update: 更新 function deleteProject() { } // delete: 删除 function handleClick() { } // handle: 事件处理 function validateEmail() { } // validate: 验证 function formatDate() { } // format: 格式化 // ❌ 避免 function data() { } // 不清楚功能 function doSomething() { } // 太模糊 function process() { } // 不明确 ``` ### 组件命名 ```typescript // ✅ 推荐