TypeScript, 技术, 编程
TypeScript 实用技巧
分享一些在实际开发中非常有用的 TypeScript 技巧和模式。
0次点击8分钟阅读
TypeScript 让代码更安全
TypeScript 为 JavaScript 添加了类型系统,让代码更加健壮和可维护。
实用技巧
1. 类型收窄
1function processValue(value: string | number) { 2 if (typeof value === 'string') { 3 // 这里 value 被推断为 string 4 return value.toUpperCase() 5 } 6 // 这里 value 被推断为 number 7 return value.toFixed(2) 8}
2. 泛型约束
1interface HasLength { 2 length: number 3} 4 5function logLength<T extends HasLength>(arg: T): T { 6 console.log(arg.length) 7 return arg 8}
3. 工具类型
TypeScript 提供了许多内置工具类型:
1// Partial - 使所有属性可选 2type PartialUser = Partial<User> 3 4// Pick - 选择特定属性 5type UserPreview = Pick<User, 'id' | 'name'> 6 7// Omit - 排除特定属性 8type UserWithoutPassword = Omit<User, 'password'> 9 10// Record - 创建键值对类型 11type PageConfig = Record<string, { title: string; description: string }>
4. 条件类型
1type IsString<T> = T extends string ? true : false 2 3type Result1 = IsString<string> // true 4type Result2 = IsString<number> // false
5. 映射类型
1type Readonly<T> = { 2 readonly [P in keyof T]: T[P] 3} 4 5type Optional<T> = { 6 [P in keyof T]?: T[P] 7}
最佳实践
- 启用严格模式: 在
tsconfig.json中设置"strict": true - 避免使用 any: 尽量使用
unknown或具体类型 - 利用类型推断: 让 TypeScript 自动推断类型
- 使用接口定义对象: 接口更适合定义对象形状
- 使用 const assertions: 使用
as const创建只读的字面量类型
示例:类型安全的 API 调用
1interface ApiResponse<T> { 2 data: T 3 error?: string 4 status: number 5} 6 7async function fetchData<T>(url: string): Promise<ApiResponse<T>> { 8 try { 9 const response = await fetch(url) 10 const data = await response.json() 11 return { 12 data, 13 status: response.status, 14 } 15 } catch (error) { 16 return { 17 data: {} as T, 18 error: error.message, 19 status: 500, 20 } 21 } 22} 23 24// 使用 25interface User { 26 id: number 27 name: string 28} 29 30const result = await fetchData<User>('/api/user') 31// result.data 的类型是 User
高级类型技巧
模板字面量类型
1type EventName = 'click' | 'scroll' | 'mousemove' 2type EventHandler = `on${Capitalize<EventName>}` 3// 结果: 'onClick' | 'onScroll' | 'onMousemove' 4 5type Route = '/user' | '/product' | '/admin' 6type FullRoute = `https://api.example.com${Route}` 7// 结果: 'https://api.example.com/user' | ...
递归类型
1type DeepPartial<T> = { 2 [P in keyof T]?: T[P] extends object 3 ? DeepPartial<T[P]> 4 : T[P] 5} 6 7interface Config { 8 server: { 9 host: string 10 port: number 11 ssl: { 12 enabled: boolean 13 cert: string 14 } 15 } 16} 17 18// 所有属性都变为可选,包括嵌套属性 19type PartialConfig = DeepPartial<Config>
联合类型辨别
1type Success = { 2 status: 'success' 3 data: string 4} 5 6type Error = { 7 status: 'error' 8 error: string 9} 10 11type Result = Success | Error 12 13function handleResult(result: Result) { 14 if (result.status === 'success') { 15 // TypeScript 知道这里是 Success 类型 16 console.log(result.data) 17 } else { 18 // TypeScript 知道这里是 Error 类型 19 console.log(result.error) 20 } 21}
infer 关键字
1// 提取 Promise 的返回类型 2type UnwrapPromise<T> = T extends Promise<infer R> ? R : T 3 4type A = UnwrapPromise<Promise<string>> // string 5type B = UnwrapPromise<number> // number 6 7// 提取函数返回类型 8type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never 9 10// 提取数组元素类型 11type ArrayElement<T> = T extends (infer E)[] ? E : never 12 13type Items = ArrayElement<string[]> // string
类型守卫进阶
自定义类型守卫
1interface Fish { 2 swim: () => void 3} 4 5interface Bird { 6 fly: () => void 7} 8 9// 自定义类型守卫 10function isFish(pet: Fish | Bird): pet is Fish { 11 return (pet as Fish).swim !== undefined 12} 13 14function move(pet: Fish | Bird) { 15 if (isFish(pet)) { 16 pet.swim() 17 } else { 18 pet.fly() 19 } 20}
断言函数
1function assert(condition: boolean, message: string): asserts condition { 2 if (!condition) { 3 throw new Error(message) 4 } 5} 6 7function processValue(value: string | null) { 8 assert(value !== null, 'Value cannot be null') 9 // TypeScript 知道这里 value 是 string 类型 10 console.log(value.toUpperCase()) 11} 12 13// 类型断言函数 14function assertIsString(value: unknown): asserts value is string { 15 if (typeof value !== 'string') { 16 throw new Error('Value must be a string') 17 } 18}
实用类型模式
构建器模式
1class QueryBuilder<T> { 2 private filters: Array<(item: T) => boolean> = [] 3 private sorters: Array<(a: T, b: T) => number> = [] 4 5 where(predicate: (item: T) => boolean): this { 6 this.filters.push(predicate) 7 return this 8 } 9 10 sortBy(compareFn: (a: T, b: T) => number): this { 11 this.sorters.push(compareFn) 12 return this 13 } 14 15 execute(data: T[]): T[] { 16 let result = data 17 18 // 应用过滤 19 for (const filter of this.filters) { 20 result = result.filter(filter) 21 } 22 23 // 应用排序 24 for (const sorter of this.sorters) { 25 result = result.sort(sorter) 26 } 27 28 return result 29 } 30} 31 32// 使用 33interface User { 34 id: number 35 name: string 36 age: number 37} 38 39const users: User[] = [...] 40 41const result = new QueryBuilder<User>() 42 .where(u => u.age > 18) 43 .where(u => u.name.startsWith('A')) 44 .sortBy((a, b) => a.age - b.age) 45 .execute(users)
状态机类型
1type State = 2 | { status: 'idle' } 3 | { status: 'loading' } 4 | { status: 'success'; data: string } 5 | { status: 'error'; error: Error } 6 7class StateMachine { 8 private state: State = { status: 'idle' } 9 10 start() { 11 if (this.state.status !== 'idle') { 12 throw new Error('Already started') 13 } 14 this.state = { status: 'loading' } 15 } 16 17 success(data: string) { 18 if (this.state.status !== 'loading') { 19 throw new Error('Not loading') 20 } 21 this.state = { status: 'success', data } 22 } 23 24 fail(error: Error) { 25 if (this.state.status !== 'loading') { 26 throw new Error('Not loading') 27 } 28 this.state = { status: 'error', error } 29 } 30 31 getState(): State { 32 return this.state 33 } 34}
类型安全的事件系统
1type EventMap = { 2 'user:login': { userId: string; timestamp: number } 3 'user:logout': { userId: string } 4 'data:update': { dataId: string; changes: object } 5} 6 7class TypedEventEmitter { 8 private listeners: { 9 [K in keyof EventMap]?: Array<(data: EventMap[K]) => void> 10 } = {} 11 12 on<K extends keyof EventMap>( 13 event: K, 14 handler: (data: EventMap[K]) => void 15 ): void { 16 if (!this.listeners[event]) { 17 this.listeners[event] = [] 18 } 19 this.listeners[event]!.push(handler) 20 } 21 22 emit<K extends keyof EventMap>(event: K, data: EventMap[K]): void { 23 const handlers = this.listeners[event] || [] 24 for (const handler of handlers) { 25 handler(data) 26 } 27 } 28} 29 30// 使用 31const emitter = new TypedEventEmitter() 32 33emitter.on('user:login', (data) => { 34 // data 的类型是 { userId: string; timestamp: number } 35 console.log(`User ${data.userId} logged in at ${data.timestamp}`) 36}) 37 38emitter.emit('user:login', { 39 userId: '123', 40 timestamp: Date.now() 41})
装饰器和元数据
类装饰器
1function Singleton<T extends { new (...args: any[]): {} }>(constructor: T) { 2 let instance: T | null = null 3 4 return class extends constructor { 5 constructor(...args: any[]) { 6 if (instance) { 7 return instance as any 8 } 9 super(...args) 10 instance = this as any 11 } 12 } 13} 14 15@Singleton 16class Database { 17 connect() { 18 console.log('Connecting to database...') 19 } 20} 21 22const db1 = new Database() 23const db2 = new Database() 24console.log(db1 === db2) // true
方法装饰器
1function Log( 2 target: any, 3 propertyKey: string, 4 descriptor: PropertyDescriptor 5) { 6 const originalMethod = descriptor.value 7 8 descriptor.value = function (...args: any[]) { 9 console.log(`Calling ${propertyKey} with args:`, args) 10 const result = originalMethod.apply(this, args) 11 console.log(`Result:`, result) 12 return result 13 } 14 15 return descriptor 16} 17 18class Calculator { 19 @Log 20 add(a: number, b: number): number { 21 return a + b 22 } 23} 24 25const calc = new Calculator() 26calc.add(2, 3) // 会打印日志
属性装饰器
1function Validate(validator: (value: any) => boolean) { 2 return function (target: any, propertyKey: string) { 3 let value: any 4 5 const getter = function () { 6 return value 7 } 8 9 const setter = function (newValue: any) { 10 if (!validator(newValue)) { 11 throw new Error(`Invalid value for ${propertyKey}`) 12 } 13 value = newValue 14 } 15 16 Object.defineProperty(target, propertyKey, { 17 get: getter, 18 set: setter, 19 enumerable: true, 20 configurable: true 21 }) 22 } 23} 24 25class User { 26 @Validate((value) => typeof value === 'string' && value.length > 0) 27 name: string = '' 28 29 @Validate((value) => typeof value === 'number' && value >= 0) 30 age: number = 0 31}
类型编程技巧
对象键的类型操作
1// 获取所有值为特定类型的键 2type FilterByType<T, U> = { 3 [K in keyof T]: T[K] extends U ? K : never 4}[keyof T] 5 6interface Person { 7 name: string 8 age: number 9 email: string 10 isAdmin: boolean 11} 12 13type StringKeys = FilterByType<Person, string> 14// 结果: 'name' | 'email' 15 16type NumberKeys = FilterByType<Person, number> 17// 结果: 'age'
深度只读
1type DeepReadonly<T> = { 2 readonly [P in keyof T]: T[P] extends object 3 ? DeepReadonly<T[P]> 4 : T[P] 5} 6 7interface MutableConfig { 8 server: { 9 host: string 10 port: number 11 options: { 12 timeout: number 13 retry: boolean 14 } 15 } 16} 17 18type ImmutableConfig = DeepReadonly<MutableConfig> 19// 所有属性和嵌套属性都变为只读
函数重载类型
1function createElement(tag: 'div'): HTMLDivElement 2function createElement(tag: 'span'): HTMLSpanElement 3function createElement(tag: 'a'): HTMLAnchorElement 4function createElement(tag: string): HTMLElement { 5 return document.createElement(tag) 6} 7 8// TypeScript 会根据参数自动推断返回类型 9const div = createElement('div') // HTMLDivElement 10const span = createElement('span') // HTMLSpanElement 11const a = createElement('a') // HTMLAnchorElement
性能优化
类型计算复杂度
1// ❌ 避免过度复杂的递归类型 2type Bad<T> = T extends any 3 ? T extends any 4 ? T extends any 5 ? T 6 : never 7 : never 8 : never 9 10// ✅ 保持类型简洁 11type Good<T> = T
使用类型别名
1// ❌ 重复的复杂类型 2function processA(data: { id: string; name: string; metadata: Record<string, unknown> }[]) {} 3function processB(data: { id: string; name: string; metadata: Record<string, unknown> }[]) {} 4 5// ✅ 使用类型别名 6type DataItem = { 7 id: string 8 name: string 9 metadata: Record<string, unknown> 10} 11 12function processA(data: DataItem[]) {} 13function processB(data: DataItem[]) {}
调试技巧
检查类型
1// 使用注释查看推断的类型 2type Test = { foo: string } 3// ^? Test 4 5// 使用 Extract 和 Exclude 6type A = Extract<'a' | 'b' | 'c', 'a' | 'b'> // 'a' | 'b' 7type B = Exclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c' 8 9// 类型断言用于调试 10type Debug<T> = { [K in keyof T]: T[K] } & {} 11type Example = Debug<{ a: string; b: number }>
类型错误定位
1// 使用 never 捕获错误 2type AssertTrue<T extends true> = T 3type Test1 = AssertTrue<true> // ✓ 4type Test2 = AssertTrue<false> // ✗ Type 'false' does not satisfy... 5 6// 类型分支调试 7type CheckType<T> = T extends string 8 ? 'is string' 9 : T extends number 10 ? 'is number' 11 : 'is other' 12 13type Test = CheckType<'hello'> // 'is string'
实战:类型安全的 API 客户端
1interface APIEndpoints { 2 '/users': { 3 GET: { 4 response: { users: User[] } 5 } 6 POST: { 7 body: { name: string; email: string } 8 response: { user: User } 9 } 10 } 11 '/users/:id': { 12 GET: { 13 params: { id: string } 14 response: { user: User } 15 } 16 PUT: { 17 params: { id: string } 18 body: Partial<User> 19 response: { user: User } 20 } 21 DELETE: { 22 params: { id: string } 23 response: { success: boolean } 24 } 25 } 26} 27 28class APIClient { 29 async request< 30 Path extends keyof APIEndpoints, 31 Method extends keyof APIEndpoints[Path] 32 >( 33 method: Method, 34 path: Path, 35 config: APIEndpoints[Path][Method] extends { body: infer B } 36 ? { body: B } & (APIEndpoints[Path][Method] extends { params: infer P } ? { params: P } : {}) 37 : APIEndpoints[Path][Method] extends { params: infer P } 38 ? { params: P } 39 : {} 40 ): Promise< 41 APIEndpoints[Path][Method] extends { response: infer R } ? R : never 42 > { 43 // 实现... 44 return {} as any 45 } 46} 47 48// 使用 49const client = new APIClient() 50 51// TypeScript 会自动推断参数和返回类型 52const users = await client.request('GET', '/users', {}) 53const user = await client.request('GET', '/users/:id', { 54 params: { id: '123' } 55}) 56const newUser = await client.request('POST', '/users', { 57 body: { name: 'John', email: 'john@example.com' } 58})
配置优化
tsconfig.json 推荐配置
1{ 2 "compilerOptions": { 3 // 严格模式 4 "strict": true, 5 "noUncheckedIndexedAccess": true, 6 "noImplicitOverride": true, 7 "noPropertyAccessFromIndexSignature": true, 8 9 // 模块解析 10 "moduleResolution": "bundler", 11 "resolveJsonModule": true, 12 "allowSyntheticDefaultImports": true, 13 "esModuleInterop": true, 14 15 // 输出 16 "target": "ES2022", 17 "module": "ESNext", 18 "lib": ["ES2022", "DOM", "DOM.Iterable"], 19 20 // 路径映射 21 "baseUrl": ".", 22 "paths": { 23 "~/*": ["./src/*"] 24 }, 25 26 // 开发体验 27 "skipLibCheck": true, 28 "forceConsistentCasingInFileNames": true, 29 "incremental": true 30 } 31}
总结
TypeScript 的类型系统虽然有一定学习曲线,但掌握这些技巧后,能显著提升代码质量和开发体验:
- 基础技巧:类型收窄、泛型约束、工具类型
- 高级类型:模板字面量、递归类型、条件类型
- 类型守卫:自定义守卫、断言函数
- 实用模式:构建器、状态机、事件系统
- 类型编程:类型计算、深度操作
- 性能优化:减少复杂度、使用别名
- 调试技巧:类型检查、错误定位
- 实战应用:类型安全的 API、表单验证
持续学习和实践这些技巧,你将能够编写更安全、更可维护的 TypeScript 代码。
