# Opentu 编码规范 本文档定义了Aitu项目的编码标准和最佳实践,确保代码质量、可维护性和团队协作效率。 ## 目录 - [总体原则](#总体原则) - [文件结构与命名](#文件结构与命名) - [TypeScript 规范](#typescript-规范) - [React 组件规范](#react-组件规范) - [CSS/SCSS 规范](#cssscss-规范) - [测试规范](#测试规范) - [Git 提交规范](#git-提交规范) - [性能优化](#性能优化) - [安全规范](#安全规范) - [文档规范](#文档规范) ## 总体原则 ### 代码哲学 - **简洁性**: 代码应该清晰、简洁、易于理解 - **一致性**: 保持整个项目的编码风格一致 - **可维护性**: 编写易于维护和扩展的代码 - **性能**: 在保证可读性的前提下追求性能 - **安全**: 始终考虑安全性,避免引入安全漏洞 ### 文件大小限制 - **单个文件不超过500行** (包括空行和注释) - 超过500行的文件应该进行合理拆分 - 例外情况需要在代码审查中说明理由 ## 文件结构与命名 ### 目录结构 ``` packages/drawnix/src/ ├── components/ # 可复用组件 │ ├── ui/ # 基础UI组件 │ ├── icons/ # 图标组件 │ └── feature/ # 功能性组件 ├── hooks/ # 自定义 Hooks ├── utils/ # 工具函数 ├── constants/ # 常量定义 ├── types/ # TypeScript 类型定义 ├── styles/ # 全局样式 ├── plugins/ # Plait 插件 └── transforms/ # 数据变换逻辑 ``` ### 命名约定 #### 文件命名 - **组件文件**: `PascalCase.tsx` (如 `ImageCropPopup.tsx`) - **Hook文件**: `camelCase.ts` (如 `useImageCrop.ts`) - **工具文件**: `kebab-case.ts` (如 `image-utils.ts`) - **类型文件**: `kebab-case.types.ts` (如 `image-crop.types.ts`) - **常量文件**: `UPPER_SNAKE_CASE.ts` (如 `STORAGE_KEYS.ts`) #### 变量命名 ```typescript // ✅ 推荐 const userName = 'alice'; const isLoading = true; const API_BASE_URL = 'https://api.example.com'; const MAX_RETRY_COUNT = 3; // ❌ 不推荐 const user_name = 'alice'; const loading = true; const apiBaseUrl = 'https://api.example.com'; ``` #### 组件命名 ```typescript // ✅ 推荐 const ImageCropPopup: React.FC = () => {}; const UserProfile = () => {}; // ❌ 不推荐 const imageCropPopup = () => {}; const userProfile = () => {}; ``` ## TypeScript 规范 ### 类型定义 #### 接口定义 ```typescript // ✅ 推荐 - 使用 interface interface User { readonly id: string; name: string; email: string; createdAt: Date; avatar?: string; // 可选属性放在最后 } // ✅ 推荐 - Props 接口 interface ImageCropPopupProps { visible: boolean; onClose: () => void; onCropSelect: (shape: CropShape) => void; children?: React.ReactNode; } ``` #### 类型别名 ```typescript // ✅ 推荐 - 联合类型使用 type type CropShape = 'rectangle' | 'circle' | 'ellipse'; type ButtonVariant = 'primary' | 'secondary' | 'outline'; // ✅ 推荐 - 复杂类型组合 type ApiResponse = { data: T; status: 'success' | 'error'; message?: string; }; ``` #### 泛型使用 ```typescript // ✅ 推荐 interface Repository { findById(id: string): Promise; create(entity: Omit): Promise; update(id: string, updates: Partial): Promise; } // ✅ 推荐 - 泛型约束 interface Identifiable { id: string; } function updateEntity(entity: T, updates: Partial): T { return { ...entity, ...updates }; } ``` ### 严格模式配置 ```json // tsconfig.json { "compilerOptions": { "strict": true, "noImplicitAny": true, "noImplicitReturns": true, "noUnusedLocals": true, "noUnusedParameters": true } } ``` ## React 组件规范 ### 组件结构 ```typescript // ✅ 推荐的组件结构 import React, { useState, useEffect, useCallback } from 'react'; import { Button } from 'tdesign-react'; import { useI18n } from '../../hooks/use-i18n'; import './ComponentName.scss'; // 1. 类型定义 interface ComponentProps { title: string; onAction: (data: string) => void; disabled?: boolean; } // 2. 常量定义 const DEFAULT_CONFIG = { timeout: 5000, retryCount: 3, }; // 3. 主组件 export const ComponentName: React.FC = ({ title, onAction, disabled = false, }) => { // 4. Hooks (固定顺序) const { language } = useI18n(); const [isLoading, setIsLoading] = useState(false); // 5. Effect hooks useEffect(() => { // 副作用逻辑 }, []); // 6. 事件处理函数 const handleClick = useCallback(() => { if (disabled) return; onAction(title); }, [disabled, onAction, title]); // 7. 渲染逻辑 return (
); }; ``` ### Hooks 使用规范 #### 自定义 Hooks ```typescript // ✅ 推荐 export const useImageCrop = (initialShape: CropShape = 'rectangle') => { const [cropShape, setCropShape] = useState(initialShape); const [isProcessing, setIsProcessing] = useState(false); const applyCrop = useCallback(async (imageUrl: string) => { setIsProcessing(true); try { // 裁剪逻辑 return processedImageUrl; } finally { setIsProcessing(false); } }, [cropShape]); return { cropShape, setCropShape, isProcessing, applyCrop, }; }; ``` #### 依赖数组规范 ```typescript // ✅ 推荐 - 完整依赖 useEffect(() => { fetchUserData(userId); }, [userId, fetchUserData]); // ✅ 推荐 - useCallback 优化 const fetchUserData = useCallback(async (id: string) => { // 实现 }, []); // ❌ 不推荐 - 缺少依赖 useEffect(() => { fetchUserData(userId); }, []); // 缺少 userId 依赖 ``` ### 状态管理 ```typescript // ✅ 推荐 - 简单状态 const [user, setUser] = useState(null); // ✅ 推荐 - 复杂状态使用 useReducer interface FormState { values: Record; errors: Record; isSubmitting: boolean; } type FormAction = | { type: 'SET_FIELD'; field: string; value: any } | { type: 'SET_ERROR'; field: string; error: string } | { type: 'SET_SUBMITTING'; isSubmitting: boolean }; const formReducer = (state: FormState, action: FormAction): FormState => { switch (action.type) { case 'SET_FIELD': return { ...state, values: { ...state.values, [action.field]: action.value } }; // 其他 case... default: return state; } }; ``` ## CSS/SCSS 规范 ### 样式架构 ```scss // ✅ 推荐的组件样式结构 .image-crop-popup { // 1. 位置相关 position: relative; z-index: 999; // 2. 盒模型 width: 300px; padding: 16px; margin: 0 auto; // 3. 外观 background: var(--color-bg-container); border: 1px solid var(--color-border); border-radius: 8px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); // 4. 字体 font-family: var(--font-family); color: var(--color-text); // 5. 动画 transition: all 0.2s ease-out; // 6. 嵌套选择器 &__header { margin-bottom: 12px; font-weight: 600; } &__content { display: flex; flex-direction: column; gap: 8px; } // 7. 状态修饰符 &--visible { opacity: 1; transform: translateY(0); } &--hidden { opacity: 0; transform: translateY(-10px); } // 8. 响应式 @media (max-width: 768px) { width: calc(100vw - 32px); padding: 12px; } } ``` ### 设计系统变量 ```scss // 使用设计系统定义的变量 :root { // 品牌色彩 --brand-primary: #F39C12; --brand-secondary: #5A4FCF; --brand-accent: #E91E63; // 渐变 --gradient-brand: linear-gradient(135deg, #F39C12 0%, #E67E22 30%, #5A4FCF 70%, #E91E63 100%); --gradient-brush: linear-gradient(135deg, #5A4FCF 0%, #7B68EE 50%, #E91E63 100%); // 间距 --spacing-xs: 4px; --spacing-sm: 8px; --spacing-md: 16px; --spacing-lg: 24px; --spacing-xl: 32px; // 字体 --font-family: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif; --font-size-xs: 12px; --font-size-sm: 14px; --font-size-base: 16px; --font-size-lg: 18px; // 圆角 --radius-sm: 6px; --radius-md: 8px; --radius-lg: 12px; // 阴影 --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.12); --shadow-md: 0 4px 20px rgba(0, 0, 0, 0.1); --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.15); } ``` ### BEM 命名规范 ```scss // ✅ 推荐 - BEM 命名 .image-crop { // Block &__popup { // Element &--visible { // Modifier opacity: 1; } } &__button { // Element &--primary { // Modifier background: var(--gradient-brand); } &--disabled { // Modifier opacity: 0.5; cursor: not-allowed; } } } ``` ## 测试规范 ### 单元测试 ```typescript // ✅ 推荐的测试结构 import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { ImageCropPopup } from './ImageCropPopup'; describe('ImageCropPopup', () => { const defaultProps = { visible: true, onClose: jest.fn(), onCropSelect: jest.fn(), }; beforeEach(() => { jest.clearAllMocks(); }); describe('渲染行为', () => { it('应该正确渲染裁剪选项', () => { render(); expect(screen.getByText('方形裁剪')).toBeInTheDocument(); expect(screen.getByText('圆形裁剪')).toBeInTheDocument(); expect(screen.getByText('椭圆形裁剪')).toBeInTheDocument(); }); it('当 visible 为 false 时应该隐藏', () => { render(); expect(screen.queryByText('方形裁剪')).not.toBeInTheDocument(); }); }); describe('交互行为', () => { it('点击裁剪选项应该调用 onCropSelect', async () => { render(); fireEvent.click(screen.getByText('圆形裁剪')); await waitFor(() => { expect(defaultProps.onCropSelect).toHaveBeenCalledWith('circle'); expect(defaultProps.onClose).toHaveBeenCalled(); }); }); }); }); ``` ### E2E 测试 ```typescript // ✅ Playwright E2E 测试示例 import { test, expect } from '@playwright/test'; test.describe('图片裁剪功能', () => { test('用户可以成功裁剪图片', async ({ page }) => { await page.goto('/'); // 上传图片 await page.setInputFiles('input[type="file"]', 'test-image.png'); // 选择图片 await page.click('[data-testid="uploaded-image"]'); // 打开裁剪菜单 await page.click('[data-testid="crop-button"]'); // 选择圆形裁剪 await page.click('text=圆形裁剪'); // 验证裁剪效果 const image = page.locator('[data-testid="cropped-image"]'); await expect(image).toHaveClass(/crop-circle/); }); }); ``` ## Git 提交规范 ### 提交消息格式 ``` ():