删库在逃程序员的Blog
置顶

TypeScript 实用技巧精选

author
·
9
0

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)