1704 字
9 分钟
TypeScript 进阶技巧:提升代码质量的实用指南
引言
TypeScript 已成为现代前端开发的事实标准。掌握其高级特性不仅能提升代码质量,还能显著提高开发效率。本文将介绍一些实用的 TypeScript 进阶技巧。
1. 类型守卫与类型收窄
自定义类型守卫
类型守卫可以帮助 TypeScript 在运行时识别具体类型:
interface Cat { type: 'cat'; meow: () => void;}
interface Dog { type: 'dog'; bark: () => void;}
type Animal = Cat | Dog;
// 使用 type predicatefunction isCat(animal: Animal): animal is Cat { return animal.type === 'cat';}
function handleAnimal(animal: Animal) { if (isCat(animal)) { animal.meow(); // TypeScript 知道这里是 Cat } else { animal.bark(); // TypeScript 知道这里是 Dog }}使用 in 操作符
interface User { name: string; email: string;}
interface Admin { name: string; permissions: string[];}
function printInfo(person: User | Admin) { console.log(person.name);
if ('permissions' in person) { console.log('Permissions:', person.permissions); } else { console.log('Email:', person.email); }}2. 泛型进阶技巧
泛型约束
interface Lengthwise { length: number;}
function logLength<T extends Lengthwise>(arg: T): T { console.log(arg.length); return arg;}
logLength('hello'); // ✅logLength([1, 2, 3]); // ✅logLength({ length: 10, value: 3 }); // ✅// logLength(123); // ❌ number 没有 length 属性泛型默认参数
interface ApiResponse<T = unknown> { data: T; status: number; message: string;}
// 使用默认类型const response: ApiResponse = { data: null, status: 200, message: 'Success'};
// 指定具体类型const userResponse: ApiResponse<User> = { data: { name: 'John', email: 'john@example.com' }, status: 200, message: 'Success'};条件类型
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // truetype B = IsString<number>; // false
// 实用示例:提取 Promise 的返回类型type Awaited<T> = T extends Promise<infer U> ? U : T;
type Result = Awaited<Promise<string>>; // stringtype Direct = Awaited<number>; // number3. 映射类型
内置工具类型
interface User { id: number; name: string; email: string; age: number;}
// Partial - 所有属性变为可选type PartialUser = Partial<User>;
// Required - 所有属性变为必需type RequiredUser = Required<PartialUser>;
// Readonly - 所有属性变为只读type ReadonlyUser = Readonly<User>;
// Pick - 选择指定属性type UserPreview = Pick<User, 'id' | 'name'>;
// Omit - 排除指定属性type UserWithoutId = Omit<User, 'id'>;
// Record - 创建对象类型type UserMap = Record<string, User>;自定义映射类型
// 将所有属性变为可为 nulltype Nullable<T> = { [P in keyof T]: T[P] | null;};
type NullableUser = Nullable<User>;
// 将所有函数属性的返回类型提取出来type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never;}[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;4. 模板字面量类型
字符串操作
// 内置字符串操作类型type Greeting = "hello world";type UppercaseGreeting = Uppercase<Greeting>; // "HELLO WORLD"type LowercaseGreeting = Lowercase<Greeting>; // "hello world"type CapitalizedGreeting = Capitalize<Greeting>; // "Hello world"type UncapitalizedGreeting = Uncapitalize<UppercaseGreeting>; // "hELLO WORLD"
// 构建路由类型type Route = '/users' | '/products' | '/orders';type APIRoute = `${Route}/${string}`;
const route1: APIRoute = '/users/123'; // ✅const route2: APIRoute = '/products/abc'; // ✅// const route3: APIRoute = '/invalid/123'; // ❌类型安全的事件系统
type EventMap = { 'user:login': { userId: string; timestamp: Date }; 'user:logout': { userId: string }; 'data:update': { id: number; data: unknown };};
type EventName = keyof EventMap;
function emit<K extends EventName>( event: K, payload: EventMap[K]): void { console.log(`Event ${event}:`, payload);}
emit('user:login', { userId: '123', timestamp: new Date()}); // ✅
// emit('user:login', { userId: '123' }); // ❌ 缺少 timestamp// emit('invalid:event', {}); // ❌ 不存在的事件5. 索引访问类型
interface User { id: number; profile: { name: string; age: number; address: { city: string; country: string; }; };}
// 访问嵌套类型type ProfileType = User['profile'];// { name: string; age: number; address: { ... } }
type AddressType = User['profile']['address'];// { city: string; country: string }
// 获取数组元素类型type StringArray = string[];type ArrayElement = StringArray[number]; // string
// 获取对象所有值的类型type UserValues = User[keyof User];// number | { name: string; age: number; ... }6. infer 关键字
提取函数参数类型
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
function greet(name: string, age: number) { console.log(`Hello ${name}, age ${age}`);}
type GreetParams = Parameters<typeof greet>; // [string, number]
// 提取第一个参数类型type FirstParameter<T> = T extends (first: infer F, ...args: any[]) => any ? F : never;
type FirstParam = FirstParameter<typeof greet>; // string提取 Promise 类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
async function fetchUser(): Promise<User> { return { id: 1, name: 'John', email: 'john@example.com', age: 30 };}
type FetchUserResult = UnwrapPromise<ReturnType<typeof fetchUser>>; // User7. 类型断言与类型收窄
双重断言
// 有时需要先断言为 unknown,再断言为目标类型const value = 'hello' as unknown as number; // 不推荐,但有时必要
// 更好的方式:使用类型守卫function isNumber(value: unknown): value is number { return typeof value === 'number';}非空断言
// 使用 ! 告诉 TypeScript 值不为 null/undefinedfunction getValue(map: Map<string, string>, key: string) { const value = map.get(key); return value!.toUpperCase(); // 确保 value 存在时使用}
// 更安全的方式function getValueSafe(map: Map<string, string>, key: string) { const value = map.get(key); if (!value) { throw new Error(`Key ${key} not found`); } return value.toUpperCase();}8. 类型编程实战
深度只读
type DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];};
interface Config { database: { host: string; port: number; credentials: { username: string; password: string; }; };}
type ReadonlyConfig = DeepReadonly<Config>;// 所有嵌套属性都变为只读深度可选
type DeepPartial<T> = { [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];};
type PartialConfig = DeepPartial<Config>;// 所有嵌套属性都变为可选路径字符串类型
type PathKeys<T> = T extends object ? { [K in keyof T]: K extends string ? T[K] extends object ? K | `${K}.${PathKeys<T[K]>}` : K : never; }[keyof T] : never;
type UserPaths = PathKeys<User>;// "id" | "profile" | "profile.name" | "profile.age" | ...9. 装饰器类型
// 类装饰器function Logger<T extends { new (...args: any[]): {} }>(constructor: T) { return class extends constructor { createdAt = new Date(); };}
@Loggerclass MyClass { name = 'MyClass';}
// 方法装饰器function Log( target: any, propertyKey: string, descriptor: PropertyDescriptor) { const original = descriptor.value;
descriptor.value = function (...args: any[]) { console.log(`Calling ${propertyKey} with`, args); return original.apply(this, args); };
return descriptor;}
class Calculator { @Log add(a: number, b: number) { return a + b; }}10. 性能优化技巧
避免过度计算
// ❌ 每次访问都重新计算type BadExample<T> = { [K in keyof T]: T[K] extends string ? Uppercase<T[K]> : T[K];};
// ✅ 使用类型别名缓存结果type ToUppercase<S> = S extends string ? Uppercase<S> : S;type GoodExample<T> = { [K in keyof T]: ToUppercase<T[K]>;};使用接口而非类型别名
// 对于对象类型,接口通常性能更好interface User { id: number; name: string;}
// 类型别名适用于联合类型、交叉类型等type ID = string | number;type Nullable<T> = T | null;最佳实践总结
- 优先使用类型推断:让 TypeScript 自动推断类型,减少手动标注
- 使用严格模式:启用
strict: true捕获更多潜在问题 - 避免 any:使用
unknown替代any,保持类型安全 - 合理使用工具类型:善用内置工具类型简化代码
- 文档化复杂类型:为复杂的类型添加注释说明
- 渐进式采用:在现有项目中逐步引入 TypeScript
结语
TypeScript 的类型系统非常强大,掌握这些进阶技巧可以帮助我们编写更安全、更易维护的代码。记住,类型系统的目的是帮助我们,而不是束缚我们。在实际开发中,要在类型安全和开发效率之间找到平衡点。
推荐资源:
- TypeScript 官方文档
- Type Challenges - TypeScript 类型挑战
- TypeScript Deep Dive - TypeScript 深入指南
TypeScript 进阶技巧:提升代码质量的实用指南
https://www.qiandulab.com/posts/typescript-advanced-tips/