# TypeScript/JavaScript Code Review Guide > TypeScript 代码审查指南,覆盖类型系统、泛型、条件类型、strict 模式、async/await 模式等核心主题。 ## 目录 - [类型安全基础](#类型安全基础) - [泛型模式](#泛型模式) - [高级类型](#高级类型) - [Strict 模式配置](#strict-模式配置) - [异步处理](#异步处理) - [不可变性](#不可变性) - [ESLint 规则](#eslint-规则) - [Review Checklist](#review-checklist) --- ## 类型安全基础 ### 避免使用 any ```typescript // ❌ Using any defeats type safety function processData(data: any) { return data.value; // 无类型检查,运行时可能崩溃 } // ✅ Use proper types interface DataPayload { value: string; } function processData(data: DataPayload) { return data.value; } // ✅ 未知类型用 unknown + 类型守卫 function processUnknown(data: unknown) { if (typeof data === 'object' && data !== null && 'value' in data) { return (data as { value: string }).value; } throw new Error('Invalid data'); } ``` ### 类型收窄 ```typescript // ❌ 不安全的类型断言 function getLength(value: string | string[]) { return (value as string[]).length; // 如果是 string 会出错 } // ✅ 使用类型守卫 function getLength(value: string | string[]): number { if (Array.isArray(value)) { return value.length; } return value.length; } // ✅ 使用 in 操作符 interface Dog { bark(): void } interface Cat { meow(): void } function speak(animal: Dog | Cat) { if ('bark' in animal) { animal.bark(); } else { animal.meow(); } } ``` ### 字面量类型与 as const ```typescript // ❌ 类型过于宽泛 const config = { endpoint: '/api', method: 'GET' // 类型是 string }; // ✅ 使用 as const 获得字面量类型 const config = { endpoint: '/api', method: 'GET' } as const; // method 类型是 'GET' // ✅ 用于函数参数 function request(method: 'GET' | 'POST', url: string) { ... } request(config.method, config.endpoint); // 正确! ``` --- ## 泛型模式 ### 基础泛型 ```typescript // ❌ 重复代码 function getFirstString(arr: string[]): string | undefined { return arr[0]; } function getFirstNumber(arr: number[]): number | undefined { return arr[0]; } // ✅ 使用泛型 function getFirst(arr: T[]): T | undefined { return arr[0]; } ``` ### 泛型约束 ```typescript // ❌ 泛型没有约束,无法访问属性 function getProperty(obj: T, key: string) { return obj[key]; // Error: 无法索引 } // ✅ 使用 keyof 约束 function getProperty(obj: T, key: K): T[K] { return obj[key]; } const user = { name: 'Alice', age: 30 }; getProperty(user, 'name'); // 返回类型是 string getProperty(user, 'age'); // 返回类型是 number getProperty(user, 'foo'); // Error: 'foo' 不在 keyof User ``` ### 泛型默认值 ```typescript // ✅ 提供合理的默认类型 interface ApiResponse { data: T; status: number; message: string; } // 可以不指定泛型参数 const response: ApiResponse = { data: null, status: 200, message: 'OK' }; // 也可以指定 const userResponse: ApiResponse = { ... }; ``` ### 常见泛型工具类型 ```typescript // ✅ 善用内置工具类型 interface User { id: number; name: string; email: string; } type PartialUser = Partial; // 所有属性可选 type RequiredUser = Required; // 所有属性必需 type ReadonlyUser = Readonly; // 所有属性只读 type UserKeys = keyof User; // 'id' | 'name' | 'email' type NameOnly = Pick; // { name: string } type WithoutId = Omit; // { name: string; email: string } type UserRecord = Record; // { [key: string]: User } ``` --- ## 高级类型 ### 条件类型 ```typescript // ✅ 根据输入类型返回不同类型 type IsString = T extends string ? true : false; type A = IsString; // true type B = IsString; // false // ✅ 提取数组元素类型 type ElementType = T extends (infer U)[] ? U : never; type Elem = ElementType; // string // ✅ 提取函数返回类型(内置 ReturnType) type MyReturnType = T extends (...args: any[]) => infer R ? R : never; ``` ### 映射类型 ```typescript // ✅ 转换对象类型的所有属性 type Nullable = { [K in keyof T]: T[K] | null; }; interface User { name: string; age: number; } type NullableUser = Nullable; // { name: string | null; age: number | null } // ✅ 添加前缀 type Getters = { [K in keyof T as `get${Capitalize}`]: () => T[K]; }; type UserGetters = Getters; // { getName: () => string; getAge: () => number } ``` ### 模板字面量类型 ```typescript // ✅ 类型安全的事件名称 type EventName = 'click' | 'focus' | 'blur'; type HandlerName = `on${Capitalize}`; // 'onClick' | 'onFocus' | 'onBlur' // ✅ API 路由类型 type ApiRoute = `/api/${string}`; const route: ApiRoute = '/api/users'; // OK const badRoute: ApiRoute = '/users'; // Error ``` ### Discriminated Unions ```typescript // ✅ 使用判别属性实现类型安全 type Result = | { success: true; data: T } | { success: false; error: E }; function handleResult(result: Result) { if (result.success) { console.log(result.data.name); // TypeScript 知道 data 存在 } else { console.log(result.error.message); // TypeScript 知道 error 存在 } } // ✅ Redux Action 模式 type Action = | { type: 'INCREMENT'; payload: number } | { type: 'DECREMENT'; payload: number } | { type: 'RESET' }; function reducer(state: number, action: Action): number { switch (action.type) { case 'INCREMENT': return state + action.payload; // payload 类型已知 case 'DECREMENT': return state - action.payload; case 'RESET': return 0; // 这里没有 payload } } ``` --- ## Strict 模式配置 ### 推荐的 tsconfig.json ```json { "compilerOptions": { // ✅ 必须开启的 strict 选项 "strict": true, "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true, "strictBindCallApply": true, "strictPropertyInitialization": true, "noImplicitThis": true, "useUnknownInCatchVariables": true, // ✅ 额外推荐选项 "noUncheckedIndexedAccess": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "exactOptionalPropertyTypes": true, "noPropertyAccessFromIndexSignature": true } } ``` ### noUncheckedIndexedAccess 的影响 ```typescript // tsconfig: "noUncheckedIndexedAccess": true const arr = [1, 2, 3]; const first = arr[0]; // 类型是 number | undefined // ❌ 直接使用可能出错 console.log(first.toFixed(2)); // Error: 可能是 undefined // ✅ 先检查 if (first !== undefined) { console.log(first.toFixed(2)); } // ✅ 或使用非空断言(确定时) console.log(arr[0]!.toFixed(2)); ``` --- ## 异步处理 ### Promise 错误处理 ```typescript // ❌ Not handling async errors async function fetchUser(id: string) { const response = await fetch(`/api/users/${id}`); return response.json(); // 网络错误未处理 } // ✅ Handle errors properly async function fetchUser(id: string): Promise { try { const response = await fetch(`/api/users/${id}`); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return await response.json(); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to fetch user: ${error.message}`); } throw error; } } ``` ### Promise.all vs Promise.allSettled ```typescript // ❌ Promise.all 一个失败全部失败 async function fetchAllUsers(ids: string[]) { const users = await Promise.all(ids.map(fetchUser)); return users; // 一个失败就全部失败 } // ✅ Promise.allSettled 获取所有结果 async function fetchAllUsers(ids: string[]) { const results = await Promise.allSettled(ids.map(fetchUser)); const users: User[] = []; const errors: Error[] = []; for (const result of results) { if (result.status === 'fulfilled') { users.push(result.value); } else { errors.push(result.reason); } } return { users, errors }; } ``` ### 竞态条件处理 ```typescript // ❌ 竞态条件:旧请求可能覆盖新请求 function useSearch() { const [query, setQuery] = useState(''); const [results, setResults] = useState([]); useEffect(() => { fetch(`/api/search?q=${query}`) .then(r => r.json()) .then(setResults); // 旧请求可能后返回! }, [query]); } // ✅ 使用 AbortController function useSearch() { const [query, setQuery] = useState(''); const [results, setResults] = useState([]); useEffect(() => { const controller = new AbortController(); fetch(`/api/search?q=${query}`, { signal: controller.signal }) .then(r => r.json()) .then(setResults) .catch(e => { if (e.name !== 'AbortError') throw e; }); return () => controller.abort(); }, [query]); } ``` --- ## 不可变性 ### Readonly 与 ReadonlyArray ```typescript // ❌ 可变参数可能被意外修改 function processUsers(users: User[]) { users.sort((a, b) => a.name.localeCompare(b.name)); // 修改了原数组! return users; } // ✅ 使用 readonly 防止修改 function processUsers(users: readonly User[]): User[] { return [...users].sort((a, b) => a.name.localeCompare(b.name)); } // ✅ 深度只读 type DeepReadonly = { readonly [K in keyof T]: T[K] extends object ? DeepReadonly : T[K]; }; ``` ### 不变式函数参数 ```typescript // ✅ 使用 as const 和 readonly 保护数据 function createConfig(routes: T) { return routes; } const routes = createConfig(['home', 'about', 'contact'] as const); // 类型是 readonly ['home', 'about', 'contact'] ``` --- ## ESLint 规则 ### 推荐的 @typescript-eslint 规则 ```javascript // .eslintrc.js module.exports = { extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended-requiring-type-checking', 'plugin:@typescript-eslint/strict' ], rules: { // ✅ 类型安全 '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-unsafe-assignment': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-call': 'error', '@typescript-eslint/no-unsafe-return': 'error', // ✅ 最佳实践 '@typescript-eslint/explicit-function-return-type': 'warn', '@typescript-eslint/no-floating-promises': 'error', '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/no-misused-promises': 'error', // ✅ 代码风格 '@typescript-eslint/consistent-type-imports': 'error', '@typescript-eslint/prefer-nullish-coalescing': 'error', '@typescript-eslint/prefer-optional-chain': 'error' } }; ``` ### 常见 ESLint 错误修复 ```typescript // ❌ no-floating-promises: Promise 必须被处理 async function save() { ... } save(); // Error: 未处理的 Promise // ✅ 显式处理 await save(); // 或 save().catch(console.error); // 或明确忽略 void save(); // ❌ no-misused-promises: 不能在非 async 位置使用 Promise const items = [1, 2, 3]; items.forEach(async (item) => { // Error! await processItem(item); }); // ✅ 使用 for...of for (const item of items) { await processItem(item); } // 或 Promise.all await Promise.all(items.map(processItem)); ``` --- ## Review Checklist ### 类型系统 - [ ] 没有使用 `any`(使用 `unknown` + 类型守卫代替) - [ ] 接口和类型定义完整且有意义的命名 - [ ] 使用泛型提高代码复用性 - [ ] 联合类型有正确的类型收窄 - [ ] 善用工具类型(Partial、Pick、Omit 等) ### 泛型 - [ ] 泛型有适当的约束(extends) - [ ] 泛型参数有合理的默认值 - [ ] 避免过度泛型化(KISS 原则) ### Strict 模式 - [ ] tsconfig.json 启用了 strict: true - [ ] 启用了 noUncheckedIndexedAccess - [ ] 没有使用 @ts-ignore(改用 @ts-expect-error) ### 异步代码 - [ ] async 函数有错误处理 - [ ] Promise rejection 被正确处理 - [ ] 没有 floating promises(未处理的 Promise) - [ ] 并发请求使用 Promise.all 或 Promise.allSettled - [ ] 竞态条件使用 AbortController 处理 ### 不可变性 - [ ] 不直接修改函数参数 - [ ] 使用 spread 操作符创建新对象/数组 - [ ] 考虑使用 readonly 修饰符 ### ESLint - [ ] 使用 @typescript-eslint/recommended - [ ] 没有 ESLint 警告或错误 - [ ] 使用 consistent-type-imports