- 移除extension模块,将功能迁移至node-agent - 修复类型导出问题,使用export type明确类型导出 - 统一数据库连接方式,从直接导入改为使用config/database - 更新文档中的项目结构描述 - 添加多个服务的实用方法,如getForecast、getBalances等 - 修复类型错误和TS1205警告 - 优化RedisService调用方式 - 添加新的实体类型定义 - 更新审计日志格式,统一字段命名
1022 lines
22 KiB
Markdown
1022 lines
22 KiB
Markdown
# 代码质量规范(Code Quality Standards)
|
||
|
||
> **模块**: 01_Architecture - 代码质量与 ESLint 规范
|
||
> **更新日期**: 2026-03-20
|
||
> **适用范围**: 全项目(dashboard、server、node-agent)
|
||
|
||
---
|
||
|
||
## 1. 概述(Overview)
|
||
|
||
### 1.1 目标
|
||
|
||
- **代码一致性**:统一代码风格和结构
|
||
- **类型安全**:通过 ESLint + TypeScript 确保类型正确
|
||
- **可维护性**:提高代码可读性和可维护性
|
||
- **自动化检查**:通过工具链自动检查代码质量
|
||
|
||
### 1.2 工具链
|
||
|
||
| 工具 | 用途 | 优先级 |
|
||
|------|------|--------|
|
||
| **ESLint** | 代码质量检查 | P0 |
|
||
| **Prettier** | 代码格式化 | P0 |
|
||
| **TypeScript** | 类型检查 | P0 |
|
||
| **Husky** | Git 钩子 | P1 |
|
||
| **lint-staged** | 提交前检查 | P1 |
|
||
|
||
---
|
||
|
||
## 2. ESLint 配置(ESLint Configuration)
|
||
|
||
### 2.1 安装依赖
|
||
|
||
```bash
|
||
# 核心依赖
|
||
npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
|
||
|
||
# 额外插件
|
||
npm install -D eslint-plugin-prettier eslint-config-prettier
|
||
npm install -D eslint-plugin-import eslint-plugin-boundaries
|
||
```
|
||
|
||
### 2.2 基础配置(.eslintrc.js)
|
||
|
||
```javascript
|
||
module.exports = {
|
||
env: {
|
||
node: true,
|
||
es2021: true,
|
||
browser: true
|
||
},
|
||
extends: [
|
||
'eslint:recommended',
|
||
'plugin:@typescript-eslint/recommended',
|
||
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||
'plugin:import/recommended',
|
||
'plugin:import/typescript',
|
||
'plugin:prettier/recommended'
|
||
],
|
||
parser: '@typescript-eslint/parser',
|
||
parserOptions: {
|
||
ecmaVersion: 'latest',
|
||
sourceType: 'module',
|
||
project: './tsconfig.json',
|
||
tsconfigRootDir: __dirname
|
||
},
|
||
plugins: [
|
||
'@typescript-eslint',
|
||
'import',
|
||
'boundaries'
|
||
],
|
||
settings: {
|
||
'import/resolver': {
|
||
typescript: {
|
||
alwaysTryTypes: true,
|
||
project: './tsconfig.json'
|
||
}
|
||
},
|
||
'boundaries/elements': [
|
||
{
|
||
type: 'controller',
|
||
pattern: 'src/api/controllers/*'
|
||
},
|
||
{
|
||
type: 'service',
|
||
pattern: 'src/services/*'
|
||
},
|
||
{
|
||
type: 'repository',
|
||
pattern: 'src/repositories/*'
|
||
},
|
||
{
|
||
type: 'model',
|
||
pattern: 'src/models/*'
|
||
},
|
||
{
|
||
type: 'dto',
|
||
pattern: 'src/dto/*'
|
||
}
|
||
]
|
||
},
|
||
rules: {
|
||
// TypeScript 核心规则
|
||
'@typescript-eslint/no-explicit-any': 'error',
|
||
'@typescript-eslint/explicit-function-return-type': 'error',
|
||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||
'@typescript-eslint/no-unused-vars': ['error', {
|
||
argsIgnorePattern: '^_',
|
||
varsIgnorePattern: '^_'
|
||
}],
|
||
'@typescript-eslint/no-floating-promises': 'error',
|
||
'@typescript-eslint/no-misused-promises': 'error',
|
||
'@typescript-eslint/strict-boolean-expressions': 'warn',
|
||
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
|
||
'@typescript-eslint/consistent-type-imports': ['error', {
|
||
prefer: 'type-imports',
|
||
disallowTypeAnnotations: false
|
||
}],
|
||
'@typescript-eslint/no-non-null-assertion': 'warn',
|
||
|
||
// Import 规则
|
||
'import/order': ['error', {
|
||
groups: [
|
||
'builtin',
|
||
'external',
|
||
'internal',
|
||
'parent',
|
||
'sibling',
|
||
'index'
|
||
],
|
||
'newlines-between': 'always',
|
||
alphabetize: {
|
||
order: 'asc',
|
||
caseInsensitive: true
|
||
}
|
||
}],
|
||
'import/no-duplicates': 'error',
|
||
'import/no-cycle': 'error',
|
||
'import/no-unresolved': 'off',
|
||
|
||
// Boundaries 规则(架构分层)
|
||
'boundaries/element-types': ['error', {
|
||
default: 'disallow',
|
||
rules: [
|
||
{
|
||
from: 'controller',
|
||
allow: ['service', 'dto']
|
||
},
|
||
{
|
||
from: 'service',
|
||
allow: ['repository', 'model', 'dto']
|
||
},
|
||
{
|
||
from: 'repository',
|
||
allow: ['model']
|
||
},
|
||
{
|
||
from: 'model',
|
||
allow: []
|
||
}
|
||
]
|
||
}],
|
||
|
||
// 通用规则
|
||
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
||
'no-debugger': 'error',
|
||
'no-alert': 'error',
|
||
'no-var': 'error',
|
||
'prefer-const': 'error',
|
||
'prefer-arrow-callback': 'error',
|
||
'no-const-assign': 'error',
|
||
'no-dupe-keys': 'error',
|
||
'no-duplicate-case': 'error',
|
||
'no-empty': 'error',
|
||
'no-eval': 'error',
|
||
'no-implied-eval': 'error',
|
||
'no-irregular-whitespace': 'error',
|
||
'no-iterator': 'error',
|
||
'no-multi-spaces': 'error',
|
||
'no-new-wrappers': 'error',
|
||
'no-return-await': 'error',
|
||
'no-sequences': 'error',
|
||
'no-shadow-restricted-names': 'error',
|
||
'no-sparse-arrays': 'error',
|
||
'no-throw-literal': 'error',
|
||
'no-unreachable': 'error',
|
||
'no-unsafe-finally': 'error',
|
||
'no-useless-concat': 'error',
|
||
'no-useless-return': 'error',
|
||
'no-with': 'error',
|
||
'radix': 'error',
|
||
'yoda': 'error',
|
||
|
||
// Prettier 集成
|
||
'prettier/prettier': 'error'
|
||
},
|
||
ignorePatterns: [
|
||
'dist',
|
||
'node_modules',
|
||
'*.config.js',
|
||
'*.config.ts',
|
||
'.umi',
|
||
'.umi-production'
|
||
]
|
||
}
|
||
```
|
||
|
||
### 2.3 模块特定配置
|
||
|
||
#### Dashboard(前端)
|
||
|
||
```javascript
|
||
module.exports = {
|
||
extends: [
|
||
'eslint:recommended',
|
||
'plugin:@typescript-eslint/recommended',
|
||
'plugin:react/recommended',
|
||
'plugin:react-hooks/recommended',
|
||
'plugin:jsx-a11y/recommended',
|
||
'plugin:prettier/recommended'
|
||
],
|
||
plugins: ['react', 'react-hooks', 'jsx-a11y'],
|
||
settings: {
|
||
react: {
|
||
version: 'detect'
|
||
}
|
||
},
|
||
rules: {
|
||
'react/react-in-jsx-scope': 'off',
|
||
'react/prop-types': 'off',
|
||
'react-hooks/rules-of-hooks': 'error',
|
||
'react-hooks/exhaustive-deps': 'warn'
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Server(后端)
|
||
|
||
```javascript
|
||
module.exports = {
|
||
env: {
|
||
node: true,
|
||
es2021: true
|
||
},
|
||
extends: [
|
||
'eslint:recommended',
|
||
'plugin:@typescript-eslint/recommended',
|
||
'plugin:prettier/recommended'
|
||
],
|
||
rules: {
|
||
'no-console': ['warn', { allow: ['warn', 'error'] }]
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Prettier 配置(Prettier Configuration)
|
||
|
||
### 3.1 安装依赖
|
||
|
||
```bash
|
||
npm install -D prettier eslint-config-prettier eslint-plugin-prettier
|
||
```
|
||
|
||
### 3.2 配置文件(.prettierrc.js)
|
||
|
||
```javascript
|
||
module.exports = {
|
||
printWidth: 100,
|
||
tabWidth: 2,
|
||
useTabs: false,
|
||
semi: true,
|
||
singleQuote: true,
|
||
quoteProps: 'as-needed',
|
||
jsxSingleQuote: false,
|
||
trailingComma: 'es5',
|
||
bracketSpacing: true,
|
||
bracketSameLine: false,
|
||
arrowParens: 'always',
|
||
proseWrap: 'preserve',
|
||
htmlWhitespaceSensitivity: 'css',
|
||
endOfLine: 'lf',
|
||
embeddedLanguageFormatting: 'auto'
|
||
}
|
||
```
|
||
|
||
### 3.3 忽略文件(.prettierignore)
|
||
|
||
```
|
||
node_modules
|
||
dist
|
||
build
|
||
coverage
|
||
*.min.js
|
||
*.min.css
|
||
package-lock.json
|
||
yarn.lock
|
||
pnpm-lock.yaml
|
||
.umi
|
||
.umi-production
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 代码风格规范(Code Style Standards)
|
||
|
||
### 4.1 命名规范
|
||
|
||
| 类型 | 命名规则 | 示例 |
|
||
|------|----------|------|
|
||
| **变量/函数** | camelCase | `getUserData`, `userName` |
|
||
| **类/接口/类型** | PascalCase | `UserService`, `Product` |
|
||
| **常量** | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT`, `API_BASE_URL` |
|
||
| **私有成员** | camelCase + `_` 前缀 | `_privateMethod`, `_internalState` |
|
||
| **枚举** | PascalCase | `Status`, `OrderStatus` |
|
||
| **文件名** | PascalCase (组件) / kebab-case (工具) | `UserList.tsx`, `api-utils.ts` |
|
||
|
||
### 4.2 注释规范
|
||
|
||
#### JSDoc 注释
|
||
|
||
```typescript
|
||
/**
|
||
* 获取用户信息
|
||
*
|
||
* @param userId - 用户ID
|
||
* @param includeProfile - 是否包含个人资料
|
||
* @returns 用户信息对象
|
||
* @throws {Error} 当用户不存在时抛出错误
|
||
*
|
||
* @example
|
||
* ```typescript
|
||
* const user = await getUser('123', true)
|
||
* console.log(user.name)
|
||
* ```
|
||
*/
|
||
async function getUser(
|
||
userId: string,
|
||
includeProfile: boolean = false
|
||
): Promise<User> {
|
||
const user = await userRepository.findById(userId)
|
||
|
||
if (!user) {
|
||
throw new Error(`User not found: ${userId}`)
|
||
}
|
||
|
||
if (includeProfile) {
|
||
return {
|
||
...user,
|
||
profile: await profileRepository.findByUserId(userId)
|
||
}
|
||
}
|
||
|
||
return user
|
||
}
|
||
```
|
||
|
||
#### 单行注释
|
||
|
||
```typescript
|
||
// 计算订单总金额
|
||
const totalAmount = calculateTotalAmount(items)
|
||
|
||
// TODO: 需要优化性能
|
||
const result = heavyComputation(data)
|
||
```
|
||
|
||
#### 多行注释
|
||
|
||
```typescript
|
||
/*
|
||
* 订单处理流程:
|
||
* 1. 验证订单数据
|
||
* 2. 检查库存
|
||
* 3. 创建支付记录
|
||
* 4. 更新订单状态
|
||
*/
|
||
```
|
||
|
||
### 4.3 代码组织
|
||
|
||
#### 文件结构
|
||
|
||
```typescript
|
||
// 1. 导入(按顺序:Node.js 内置、第三方库、内部模块)
|
||
import { promises as fs } from 'fs'
|
||
import axios from 'axios'
|
||
import { UserService } from '@/services/UserService'
|
||
import type { User } from '@/types/domain/User'
|
||
|
||
// 2. 类型定义
|
||
interface UserDTO {
|
||
id: string
|
||
name: string
|
||
email: string
|
||
}
|
||
|
||
// 3. 常量定义
|
||
const MAX_RETRY_COUNT = 3
|
||
const API_TIMEOUT = 5000
|
||
|
||
// 4. 类/函数定义
|
||
export class UserController {
|
||
// ...
|
||
}
|
||
|
||
// 5. 导出
|
||
export { UserDTO }
|
||
```
|
||
|
||
#### 导入顺序
|
||
|
||
```typescript
|
||
// 1. Node.js 内置模块
|
||
import { promises as fs } from 'fs'
|
||
import path from 'path'
|
||
|
||
// 2. 第三方库
|
||
import axios from 'axios'
|
||
import { z } from 'zod'
|
||
|
||
// 3. 内部模块(按路径层级)
|
||
import { UserService } from '@/services/UserService'
|
||
import { UserRepository } from '@/repositories/UserRepository'
|
||
import type { User } from '@/types/domain/User'
|
||
import { validateUser } from '@/utils/validators'
|
||
|
||
// 4. 相对路径导入
|
||
import { formatDate } from './utils/date'
|
||
```
|
||
|
||
---
|
||
|
||
## 5. TypeScript 特定规范(TypeScript Specific Standards)
|
||
|
||
### 5.1 类型定义
|
||
|
||
```typescript
|
||
// ✅ 正确:使用 interface 定义对象类型
|
||
interface User {
|
||
id: string
|
||
name: string
|
||
email: string
|
||
}
|
||
|
||
// ✅ 正确:使用 type 定义联合类型、交叉类型
|
||
type Status = 'active' | 'inactive' | 'pending'
|
||
type UserWithProfile = User & { profile: Profile }
|
||
|
||
// ❌ 错误:使用 type 定义简单对象类型
|
||
type User = {
|
||
id: string
|
||
name: string
|
||
email: string
|
||
}
|
||
```
|
||
|
||
### 5.2 泛型使用
|
||
|
||
```typescript
|
||
// ✅ 正确:使用泛型提高代码复用性
|
||
function fetchData<T>(url: string): Promise<T> {
|
||
return axios.get<T>(url).then(res => res.data)
|
||
}
|
||
|
||
// ✅ 正确:使用泛型约束
|
||
function processItem<T extends { id: string }>(item: T): void {
|
||
console.log(item.id)
|
||
}
|
||
|
||
// ❌ 错误:过度使用泛型
|
||
function process<T, U, V, W>(a: T, b: U, c: V, d: W): void {
|
||
// ...
|
||
}
|
||
```
|
||
|
||
### 5.3 类型守卫
|
||
|
||
```typescript
|
||
// ✅ 正确:使用类型守卫
|
||
function isProduct(data: unknown): data is Product {
|
||
return (
|
||
typeof data === 'object' &&
|
||
data !== null &&
|
||
'id' in data &&
|
||
'name' in data &&
|
||
'price' in data
|
||
)
|
||
}
|
||
|
||
function handleData(data: unknown) {
|
||
if (isProduct(data)) {
|
||
// TypeScript 知道 data 是 Product 类型
|
||
console.log(data.name)
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.4 可选链和空值合并
|
||
|
||
```typescript
|
||
// ✅ 正确:使用可选链
|
||
const userName = user?.profile?.name
|
||
|
||
// ✅ 正确:使用空值合并
|
||
const displayName = userName ?? 'Anonymous'
|
||
|
||
// ✅ 正确:结合使用
|
||
const value = obj?.prop ?? defaultValue
|
||
|
||
// ❌ 错误:过度使用可选链
|
||
const value = obj?.prop?.nested?.value?.toString()
|
||
```
|
||
|
||
---
|
||
|
||
## 6. React 特定规范(React Specific Standards)
|
||
|
||
### 6.1 组件定义
|
||
|
||
```typescript
|
||
// ✅ 正确:使用函数组件 + TypeScript
|
||
interface Props {
|
||
title: string
|
||
count: number
|
||
onAction: () => void
|
||
}
|
||
|
||
export function ProductCard({ title, count, onAction }: Props) {
|
||
return (
|
||
<div className="product-card">
|
||
<h2>{title}</h2>
|
||
<p>Count: {count}</p>
|
||
<button onClick={onAction}>Action</button>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// ✅ 正确:使用 React.FC(不推荐,但可用)
|
||
export const ProductCard: React.FC<Props> = ({ title, count, onAction }) => {
|
||
return (
|
||
<div className="product-card">
|
||
<h2>{title}</h2>
|
||
<p>Count: {count}</p>
|
||
<button onClick={onAction}>Action</button>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
### 6.2 Hooks 使用
|
||
|
||
```typescript
|
||
// ✅ 正确:使用自定义 Hook
|
||
function useUserData(userId: string) {
|
||
const [user, setUser] = useState<User | null>(null)
|
||
const [loading, setLoading] = useState(true)
|
||
const [error, setError] = useState<Error | null>(null)
|
||
|
||
useEffect(() => {
|
||
async function fetchUser() {
|
||
try {
|
||
setLoading(true)
|
||
const data = await userService.getUser(userId)
|
||
setUser(data)
|
||
} catch (err) {
|
||
setError(err as Error)
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
fetchUser()
|
||
}, [userId])
|
||
|
||
return { user, loading, error }
|
||
}
|
||
|
||
// ✅ 正确:在组件中使用
|
||
function UserProfile({ userId }: { userId: string }) {
|
||
const { user, loading, error } = useUserData(userId)
|
||
|
||
if (loading) return <div>Loading...</div>
|
||
if (error) return <div>Error: {error.message}</div>
|
||
if (!user) return <div>User not found</div>
|
||
|
||
return <div>{user.name}</div>
|
||
}
|
||
```
|
||
|
||
### 6.3 Props 类型定义
|
||
|
||
```typescript
|
||
// ✅ 正确:明确的 Props 类型
|
||
interface ProductListProps {
|
||
products: Product[]
|
||
loading?: boolean
|
||
onProductClick: (product: Product) => void
|
||
className?: string
|
||
}
|
||
|
||
export function ProductList({
|
||
products,
|
||
loading = false,
|
||
onProductClick,
|
||
className
|
||
}: ProductListProps) {
|
||
if (loading) return <div>Loading...</div>
|
||
|
||
return (
|
||
<div className={className}>
|
||
{products.map(product => (
|
||
<ProductCard
|
||
key={product.id}
|
||
product={product}
|
||
onClick={() => onProductClick(product)}
|
||
/>
|
||
))}
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 后端特定规范(Backend Specific Standards)
|
||
|
||
### 7.1 Controller 层
|
||
|
||
```typescript
|
||
// ✅ 正确:Controller 只负责请求/响应
|
||
export class UserController {
|
||
constructor(private readonly userService: UserService) {}
|
||
|
||
async getUser(req: Request, res: Response): Promise<void> {
|
||
try {
|
||
const { userId } = req.params
|
||
const user = await this.userService.getUser(userId)
|
||
res.json({ success: true, data: user })
|
||
} catch (error) {
|
||
res.status(500).json({
|
||
success: false,
|
||
error: error instanceof Error ? error.message : 'Unknown error'
|
||
})
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 7.2 Service 层
|
||
|
||
```typescript
|
||
// ✅ 正确:Service 负责业务逻辑
|
||
export class UserService {
|
||
constructor(
|
||
private readonly userRepository: UserRepository,
|
||
private readonly profileRepository: ProfileRepository
|
||
) {}
|
||
|
||
async getUser(userId: string): Promise<UserWithProfile> {
|
||
const user = await this.userRepository.findById(userId)
|
||
|
||
if (!user) {
|
||
throw new Error(`User not found: ${userId}`)
|
||
}
|
||
|
||
const profile = await this.profileRepository.findByUserId(userId)
|
||
|
||
return {
|
||
...user,
|
||
profile
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 7.3 Repository 层
|
||
|
||
```typescript
|
||
// ✅ 正确:Repository 只负责数据访问
|
||
export class UserRepository {
|
||
constructor(private readonly db: Knex) {}
|
||
|
||
async findById(id: string): Promise<User | null> {
|
||
const row = await this.db('users').where({ id }).first()
|
||
return row ? this.mapToEntity(row) : null
|
||
}
|
||
|
||
private mapToEntity(row: any): User {
|
||
return {
|
||
id: row.id,
|
||
name: row.name,
|
||
email: row.email,
|
||
createdAt: new Date(row.created_at),
|
||
updatedAt: new Date(row.updated_at)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 测试规范(Testing Standards)
|
||
|
||
### 8.1 测试文件命名
|
||
|
||
```
|
||
src/
|
||
services/
|
||
UserService.ts
|
||
UserService.test.ts # 单元测试
|
||
UserService.integration.test.ts # 集成测试
|
||
```
|
||
|
||
### 8.2 测试结构
|
||
|
||
```typescript
|
||
describe('UserService', () => {
|
||
let userService: UserService
|
||
let mockUserRepository: jest.Mocked<UserRepository>
|
||
|
||
beforeEach(() => {
|
||
mockUserRepository = {
|
||
findById: jest.fn(),
|
||
create: jest.fn(),
|
||
update: jest.fn(),
|
||
delete: jest.fn()
|
||
} as any
|
||
|
||
userService = new UserService(mockUserRepository)
|
||
})
|
||
|
||
describe('getUser', () => {
|
||
it('should return user when user exists', async () => {
|
||
const mockUser = {
|
||
id: '123',
|
||
name: 'John Doe',
|
||
email: 'john@example.com'
|
||
}
|
||
|
||
mockUserRepository.findById.mockResolvedValue(mockUser)
|
||
|
||
const result = await userService.getUser('123')
|
||
|
||
expect(result).toEqual(mockUser)
|
||
expect(mockUserRepository.findById).toHaveBeenCalledWith('123')
|
||
})
|
||
|
||
it('should throw error when user does not exist', async () => {
|
||
mockUserRepository.findById.mockResolvedValue(null)
|
||
|
||
await expect(userService.getUser('123')).rejects.toThrow('User not found: 123')
|
||
})
|
||
})
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 性能优化规范(Performance Standards)
|
||
|
||
### 9.1 避免不必要的渲染
|
||
|
||
```typescript
|
||
// ✅ 正确:使用 React.memo
|
||
export const ProductCard = React.memo(function ProductCard({ product }: { product: Product }) {
|
||
return <div>{product.name}</div>
|
||
})
|
||
|
||
// ✅ 正确:使用 useMemo
|
||
function ProductList({ products }: { products: Product[] }) {
|
||
const sortedProducts = useMemo(
|
||
() => [...products].sort((a, b) => a.name.localeCompare(b.name)),
|
||
[products]
|
||
)
|
||
|
||
return (
|
||
<div>
|
||
{sortedProducts.map(product => (
|
||
<ProductCard key={product.id} product={product} />
|
||
))}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// ✅ 正确:使用 useCallback
|
||
function ProductList({ products, onProductClick }: { products: Product[], onProductClick: (product: Product) => void }) {
|
||
const handleClick = useCallback(
|
||
(product: Product) => {
|
||
onProductClick(product)
|
||
},
|
||
[onProductClick]
|
||
)
|
||
|
||
return (
|
||
<div>
|
||
{products.map(product => (
|
||
<ProductCard key={product.id} product={product} onClick={handleClick} />
|
||
))}
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
### 9.2 避免内存泄漏
|
||
|
||
```typescript
|
||
// ✅ 正确:清理副作用
|
||
function useInterval(callback: () => void, delay: number) {
|
||
useEffect(() => {
|
||
const intervalId = setInterval(callback, delay)
|
||
|
||
return () => {
|
||
clearInterval(intervalId)
|
||
}
|
||
}, [callback, delay])
|
||
}
|
||
|
||
// ✅ 正确:取消请求
|
||
function useUserData(userId: string) {
|
||
const [user, setUser] = useState<User | null>(null)
|
||
const [loading, setLoading] = useState(true)
|
||
|
||
useEffect(() => {
|
||
const abortController = new AbortController()
|
||
|
||
async function fetchUser() {
|
||
try {
|
||
setLoading(true)
|
||
const data = await userService.getUser(userId, { signal: abortController.signal })
|
||
setUser(data)
|
||
} catch (error) {
|
||
if (error.name !== 'AbortError') {
|
||
console.error(error)
|
||
}
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
fetchUser()
|
||
|
||
return () => {
|
||
abortController.abort()
|
||
}
|
||
}, [userId])
|
||
|
||
return { user, loading }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 安全规范(Security Standards)
|
||
|
||
### 10.1 输入验证
|
||
|
||
```typescript
|
||
// ✅ 正确:使用 zod 验证输入
|
||
const CreateUserSchema = z.object({
|
||
name: z.string().min(1).max(100),
|
||
email: z.string().email(),
|
||
password: z.string().min(8).regex(/[A-Z]/).regex(/[0-9]/)
|
||
})
|
||
|
||
async function createUser(data: unknown): Promise<User> {
|
||
const validatedData = CreateUserSchema.parse(data)
|
||
return userRepository.create(validatedData)
|
||
}
|
||
```
|
||
|
||
### 10.2 SQL 注入防护
|
||
|
||
```typescript
|
||
// ✅ 正确:使用参数化查询
|
||
async function getUserById(id: string): Promise<User | null> {
|
||
const row = await this.db('users').where({ id }).first()
|
||
return row ? this.mapToEntity(row) : null
|
||
}
|
||
|
||
// ❌ 错误:字符串拼接 SQL
|
||
async function getUserByIdBad(id: string): Promise<User | null> {
|
||
const row = await this.db.raw(`SELECT * FROM users WHERE id = '${id}'`)
|
||
return row ? this.mapToEntity(row) : null
|
||
}
|
||
```
|
||
|
||
### 10.3 XSS 防护
|
||
|
||
```typescript
|
||
// ✅ 正确:React 自动转义
|
||
function UserDisplay({ name }: { name: string }) {
|
||
return <div>{name}</div>
|
||
}
|
||
|
||
// ✅ 正确:使用 DOMPurify 清理 HTML
|
||
import DOMPurify from 'dompurify'
|
||
|
||
function RichTextContent({ html }: { html: string }) {
|
||
const cleanHtml = DOMPurify.sanitize(html)
|
||
return <div dangerouslySetInnerHTML={{ __html: cleanHtml }} />
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 11. Git 提交规范(Git Commit Standards)
|
||
|
||
### 11.1 提交信息格式
|
||
|
||
```
|
||
<type>(<scope>): <subject>
|
||
|
||
<body>
|
||
|
||
<footer>
|
||
```
|
||
|
||
### 11.2 Type 类型
|
||
|
||
| Type | 说明 | 示例 |
|
||
|------|------|------|
|
||
| `feat` | 新功能 | `feat(user): add user registration` |
|
||
| `fix` | 修复 bug | `fix(auth): resolve login issue` |
|
||
| `docs` | 文档更新 | `docs(readme): update installation guide` |
|
||
| `style` | 代码格式 | `style: format code with prettier` |
|
||
| `refactor` | 重构 | `refactor(service): simplify user service` |
|
||
| `perf` | 性能优化 | `perf(api): optimize database query` |
|
||
| `test` | 测试 | `test(user): add user service tests` |
|
||
| `chore` | 构建/工具 | `chore(deps): update dependencies` |
|
||
|
||
### 11.3 示例
|
||
|
||
```
|
||
feat(user): add user registration feature
|
||
|
||
- Add user registration endpoint
|
||
- Implement email verification
|
||
- Add user profile creation
|
||
|
||
Closes #123
|
||
```
|
||
|
||
---
|
||
|
||
## 12. 自动化检查流程(Automated Check Process)
|
||
|
||
### 12.1 开发流程
|
||
|
||
```
|
||
写代码
|
||
↓
|
||
ESLint 实时报错(IDE 插件)
|
||
↓
|
||
Prettier 自动格式化(保存时)
|
||
↓
|
||
提交前 lint-staged 拦截
|
||
↓
|
||
CI 执行完整检查
|
||
↓
|
||
代码审查
|
||
↓
|
||
合并
|
||
```
|
||
|
||
### 12.2 package.json 脚本
|
||
|
||
```json
|
||
{
|
||
"scripts": {
|
||
"lint": "eslint . --ext .ts,.tsx,.js,.jsx",
|
||
"lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx --fix",
|
||
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
|
||
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md}\"",
|
||
"typecheck": "tsc --noEmit",
|
||
"test": "jest",
|
||
"test:coverage": "jest --coverage",
|
||
"validate": "npm run lint && npm run format:check && npm run typecheck && npm run test",
|
||
"prepare": "husky install"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 12.3 lint-staged 配置
|
||
|
||
```json
|
||
{
|
||
"lint-staged": {
|
||
"*.{ts,tsx}": [
|
||
"eslint --fix",
|
||
"prettier --write"
|
||
],
|
||
"*.{js,jsx}": [
|
||
"eslint --fix",
|
||
"prettier --write"
|
||
],
|
||
"*.{json,md}": [
|
||
"prettier --write"
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 13. 版本管理
|
||
|
||
| 版本 | 变更内容 | 日期 |
|
||
|------|----------|------|
|
||
| 1.0.0 | 初始版本 | 2026-03-20 |
|
||
|
||
---
|
||
|
||
## 14. 参考资源
|
||
|
||
- [ESLint 官方文档](https://eslint.org/docs/latest/)
|
||
- [Prettier 官方文档](https://prettier.io/docs/en/)
|
||
- [TypeScript ESLint 规则](https://typescript-eslint.io/rules/)
|
||
- [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript)
|
||
- [Google TypeScript Style Guide](https://google.github.io/styleguide/tsguide.html)
|