置顶
TypeScript 实用技巧精选
TypeScript 实用技巧精选
提升代码质量与开发效率的 TS 高级用法,让你从"会用"到"用好"。
一、泛型(Generics)进阶
1.1 泛型约束
// 基础泛型 - 可以接收任何类型
function identity<T>(arg: T): T {
return arg;
}
// 泛型约束 - 要求 T 必须有 length 属性
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
loggingIdentity('hello'); // ✅
loggingIdentity([1, 2, 3]); // ✅
loggingIdentity(123); // ❌ number 没有 length
1.2 多个泛型约束
function merge<T extends object, U extends object>(
obj1: T,
obj2: U
): T & U {
return { ...obj1, ...obj2 };
}
const result = merge({ name: '张三' }, { age: 18 });
// 类型:{ name: string } & { age: number }
1.3 泛型默认值
interface Response<T = any> {
code: number;
data: T;
message: string;
}
// 不传泛型参数时使用默认值 any
const res1: Response = { code: 0, data: {}, message: 'ok' };
// 指定具体类型
const res2: Response<{ id: number }> = {
code: 0,
data: { id: 1 },
message: 'ok'
};
二、工具类型(Utility Types)
2.1 Partial - 全部可选
interface User {
id: number;
name: string;
email: string;
}
// 所有属性变为可选
type PartialUser = Partial<User>;
// 等价于:{ id?: number; name?: string; email?: string; }
// 应用场景:更新用户信息
function updateUser(id: number, data: Partial<User>) {
// ...
}
2.2 Required - 全部必填
type RequiredUser = Required<PartialUser>;
// 所有属性变为必填
2.3 Pick - 挑选部分属性
// 只挑选 id 和 name
type UserBase = Pick<User, 'id' | 'name'>;
// 等价于:{ id: number; name: string; }
2.4 Omit - 省略部分属性
// 省略 email
type UserWithoutEmail = Omit<User, 'email'>;
// 等价于:{ id: number; name: string; }
2.5 Record - 定义对象类型
// 定义键为 string,值为 number 的对象
type ScoreMap = Record<string, number>;
const scores: ScoreMap = {
math: 90,
english: 85
};
// 更复杂的用法
type UserRole = 'admin' | 'user' | 'guest';
interface Permission {
canRead: boolean;
canWrite: boolean;
}
const rolePermissions: Record<UserRole, Permission> = {
admin: { canRead: true, canWrite: true },
user: { canRead: true, canWrite: false },
guest: { canRead: false, canWrite: false }
};
2.6 Exclude / Extract / NonNullable
type T1 = Exclude<'a' | 'b' | 'c', 'a'>; // 'b' | 'c'
type T2 = Extract<'a' | 'b' | 'c', 'a' | 'b'>; // 'a' | 'b'
type T3 = NonNullable<string | null | undefined>; // string
三、类型守卫(Type Guards)
3.1 typeof 守卫
function padLeft(value: string, padding: string | number) {
if (typeof padding === 'number') {
return ' '.repeat(padding) + value;
}
if (typeof padding === 'string') {
return padding + value;
}
throw new Error('Invalid padding');
}
3.2 instanceof 守卫
class Bird {
fly() { console.log('flying'); }
layEggs() { console.log('laying eggs'); }
}
class Fish {
swim() { console.log('swimming'); }
layEggs() { console.log('laying eggs'); }
}
function move(pet: Bird | Fish) {
if (pet instanceof Bird) {
pet.fly();
} else {
pet.swim();
}
}
3.3 in 守卫
interface A {
a: string;
}
interface B {
b: string;
}
function print(x: A | B) {
if ('a' in x) {
console.log(x.a);
} else {
console.log(x.b);
}
}
3.4 自定义类型守卫
interface Cat {
meow(): void;
}
interface Dog {
bark(): void;
}
// 自定义类型谓词
function isCat(pet: Cat | Dog): pet is Cat {
return (pet as Cat).meow !== undefined;
}
function makeSound(pet: Cat | Dog) {
if (isCat(pet)) {
pet.meow(); // TS 知道这里是 Cat
} else {
pet.bark(); // TS 知道这里是 Dog
}
}
四、条件类型(Conditional Types)
4.1 基础语法
// T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
type T1 = IsString<string>; // true
type T2 = IsString<number>; // false
4.2 分布式条件类型
// 条件类型会自动分发到联合类型的每个成员
type ToArray<T> = T extends any ? T[] : never;
type T1 = ToArray<string | number>;
// 等价于:ToArray<string> | ToArray<number>
// 结果:string[] | number[]
4.3 infer 关键字
// 提取 Promise 的类型
type UnpackPromise<T> = T extends Promise<infer U> ? U : T;
type T1 = UnpackPromise<Promise<string>>; // string
type T2 = UnpackPromise<number>; // number
// 提取函数返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
type T3 = ReturnType<() => string>; // string
五、映射类型(Mapped Types)
5.1 基础映射
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface Person {
name: string;
age: number;
}
type ReadonlyPerson = Readonly<Person>;
// { readonly name: string; readonly age: number; }
5.2 添加/移除修饰符
// 移除 readonly
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
// 移除可选
type Required<T> = {
[P in keyof T]-?: T[P];
};
5.3 键的重映射(TS 4.1+)
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number; }
六、实用技巧
6.1 安全的数组访问
// 错误:可能返回 undefined
function getFirst(arr: string[]): string {
return arr[0]; // ❌
}
// 正确
function getFirst(arr: string[]): string | undefined {
return arr[0]; // ✅
}
// 或者使用非空断言(确保不为空时)
function getFirst(arr: string[]): string {
return arr[0]!; // ✅ 告诉 TS 不会是 undefined
}
6.2 枚举转对象
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
}
// 获取枚举所有值
const directions = Object.values(Direction);
// ['UP', 'DOWN', 'LEFT', 'RIGHT']
6.3 常量断言
// 普通数组 - 类型是 string[]
const arr1 = ['a', 'b', 'c'];
// 常量断言 - 类型是 readonly ['a', 'b', 'c']
const arr2 = ['a', 'b', 'c'] as const;
// 应用场景:配置项
const CONFIG = {
API_URL: 'https://api.example.com',
TIMEOUT: 5000
} as const;
// CONFIG.API_URL 的类型是字面量类型,不是 string
6.4 函数重载
// 重载签名
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: string, b: number): string;
function add(a: number, b: string): string;
// 实现签名
function add(a: any, b: any): any {
if (typeof a === 'number' && typeof b === 'number') {
return a + b;
}
if (typeof a === 'string' && typeof b === 'string') {
return a + b;
}
return String(a) + String(b);
}
add(1, 2); // ✅ number
add('a', 'b'); // ✅ string
add(1, 'b'); // ✅ string
6.5 排除 never 类型
type Filtered = Exclude<string | number | never, never>;
// string | number
// 应用场景:条件过滤
type KeysOfType<T, V> = {
[K in keyof T]: T[K] extends V ? K : never;
}[keyof T];
interface User {
id: number;
name: string;
email: string;
}
type StringKeys = KeysOfType<User, string>;
// 'name' | 'email'
七、最佳实践
7.1 避免使用 any
// ❌ 不好
function process(data: any) {
return data.value;
}
// ✅ 好
function process<T>(data: T): T {
return data;
}
// ✅ 或者使用 unknown
function process(data: unknown) {
if (typeof data === 'object' && data !== null) {
// 类型守卫后使用
}
}
7.2 使用接口定义对象
// ✅ 推荐
interface User {
id: number;
name: string;
}
// ⚠️ 也可以用 type
type User = {
id: number;
name: string;
};
// 区别:interface 支持声明合并,type 支持更复杂的类型运算
7.3 利用类型推断
// ✅ 不需要显式标注
const name: string = '张三'; // 冗余
const name = '张三'; // 简洁
// ✅ 函数返回值通常也不需要
function add(a: number, b: number) {
return a + b; // TS 能推断出返回 number
}
参考资源:
评论 (0)