折腾侠
技术教程

TypeScript高级类型系统:从泛型到条件类型的完全指南

深入讲解TypeScript的泛型、条件类型、映射类型和模板字面量类型,写出真正类型安全的代码

折腾侠
2026/03/19 发布
21约 6 分钟577 字 / 824 词00

引言

TypeScript的类型系统是其最强大的特性之一。许多开发者只停留在基础类型注解层面,没有充分利用TypeScript的高级特性。掌握泛型、条件类型、映射类型等高级特性,能让你的代码既有完整的类型安全,又不失灵活性。

一、泛型的高级用法

1.1 泛型约束

TypeScript
// 约束T必须有length属性
function getLength<T extends { length: number }>(arr: T): number {
  return arr.length;
}

getLength([1, 2, 3]);     // OK
getLength('hello');        // OK
getLength({ length: 5 }); // OK
getLength(42);             // Error: number没有length属性

// keyof约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: 'Alice', age: 28 };
getProperty(user, 'name');  // 返回类型是string
getProperty(user, 'age');   // 返回类型是number
getProperty(user, 'email'); // Error: 'email'不是user的键

1.2 条件类型

条件类型让类型系统具备了if-else的能力:

TypeScript
type IsString<T> = T extends string ? 'yes' : 'no';

type A = IsString<string>;  // 'yes'
type B = IsString<number>;  // 'no'

// infer:在条件类型中推断类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type FnReturn = ReturnType<() => string>;      // string
type FnReturn2 = ReturnType<() => Promise<number>>; // Promise<number>

// 提取Promise的值类型
type Awaited<T> = T extends Promise<infer V> ? Awaited<V> : T;

type Val = Awaited<Promise<Promise<string>>>; // string

1.3 分布式条件类型

当条件类型用于联合类型时,会自动分发:

TypeScript
type ToArray<T> = T extends any ? T[] : never;

// 分布式行为:string | number 分别应用
type Arr = ToArray<string | number>;  // string[] | number[]

// 如果想避免分布,用元组包裹
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type Arr2 = ToArrayNonDist<string | number>;  // (string | number)[]

二、映射类型

2.1 基础映射类型

TypeScript
// 将所有属性变为可选
type Partial<T> = {
  [K in keyof T]?: T[K];
};

// 将所有属性变为只读
type Readonly<T> = {
  readonly [K in keyof T]: T[K];
};

// 将所有属性变为必填(去掉可选)
type Required<T> = {
  [K in keyof T]-?: T[K];  // -? 移除可选修饰符
};

// 从T中选取指定属性
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

2.2 键重映射(as子句)

TypeScript
// 将所有属性名转为getter形式
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

interface User {
  name: string;
  age: number;
}

type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number; }

// 过滤属性:排除值类型为函数的属性
type NonFunctionProperties<T> = {
  [K in keyof T as T[K] extends Function ? never : K]: T[K];
};

三、模板字面量类型

TypeScript
type EventName = 'click' | 'focus' | 'blur';
type Handler = `on${Capitalize<EventName>}`;
// 'onClick' | 'onFocus' | 'onBlur'

// 实用工具:从对象类型生成事件类型
type Events<T extends string> = {
  [K in T as `on${Capitalize<K>}`]?: (event: Event) => void;
};

// CSS属性类型
type CSSUnit = 'px' | 'em' | 'rem' | '%';
type CSSValue = `${number}${CSSUnit}`;
// '100px' | '1.5em' 等

四、实用工具类型实现

4.1 DeepPartial

TypeScript
type DeepPartial<T> = T extends object
  ? { [K in keyof T]?: DeepPartial<T[K]> }
  : T;

interface Config {
  server: {
    host: string;
    port: number;
    ssl: { cert: string; key: string };
  };
  database: { url: string };
}

// DeepPartial<Config> 所有嵌套属性都可选
function updateConfig(patch: DeepPartial<Config>) { ... }

4.2 路径类型(深度keyof)

TypeScript
type DeepKeys<T, Prefix extends string = ''> = {
  [K in keyof T & string]: T[K] extends object
    ? DeepKeys<T[K], `${Prefix}${K}.`>
    : `${Prefix}${K}`;
}[keyof T & string];

type UserKeys = DeepKeys<{
  name: string;
  address: { city: string; zip: string };
}>;
// 'name' | 'address.city' | 'address.zip'

4.3 函数重载类型

TypeScript
function process(x: string): string;
function process(x: number): number;
function process(x: string | number): string | number {
  if (typeof x === 'string') return x.toUpperCase();
  return x * 2;
}

// 或者用条件类型实现同样效果
type Process<T extends string | number> =
  T extends string ? string : number;

declare function process2<T extends string | number>(x: T): Process<T>;

五、类型推断技巧

5.1 从数据推断类型(satisfies)

TypeScript
// satisfies:让值满足类型约束,同时保留最具体的类型
const routes = {
  home: '/',
  about: '/about',
  user: (id: number) => `/user/${id}`,
} satisfies Record<string, string | ((id: number) => string)>;

// routes.home 的类型是 '/'(字面量类型),不是 string
// routes.user 的类型是 (id: number) => string

5.2 NoInfer(TypeScript 5.4+)

TypeScript
// 防止某个位置触发类型推断
function createState<T>(initial: T, fallback: NoInfer<T>): T {
  return initial ?? fallback;
}

// T 只从 initial 推断,fallback 不影响推断
createState(1, 2);      // T = number, OK
createState(1, 'two');  // Error: 'two'不是number类型

六、类型安全的设计模式

6.1 区分联合类型(Discriminated Union)

TypeScript
type Result<T, E = Error> =
  | { success: true; data: T }
  | { success: false; error: E };

function fetchUser(id: string): Promise<Result<User>> {
  try {
    const user = await api.getUser(id);
    return { success: true, data: user };
  } catch (error) {
    return { success: false, error: error as Error };
  }
}

// 使用时TypeScript会自动缩窄类型
const result = await fetchUser('123');
if (result.success) {
  console.log(result.data.name);  // data的类型是User
} else {
  console.error(result.error.message);  // error的类型是Error
}

6.2 Builder模式的类型安全实现

TypeScript
type BuilderState = {
  name: string;
  age: number;
  email: string;
};

class UserBuilder<State extends Partial<BuilderState> = {}> {
  private state: State;

  setName(name: string): UserBuilder<State & { name: string }> {
    return new UserBuilder({ ...this.state, name });
  }

  setAge(age: number): UserBuilder<State & { age: number }> {
    return new UserBuilder({ ...this.state, age });
  }

  // 只有name和age都设置后才能build
  build(
    this: UserBuilder<State & { name: string; age: number }>
  ): BuilderState {
    return this.state as BuilderState;
  }
}

结语

TypeScript的类型系统本质上是一门独立的图灵完备语言。深入理解这些高级特性,不仅能帮你写出类型安全的代码,更能让你设计出优雅的API——让调用者在写代码时得到精确的类型提示和编译期错误检查,大幅减少运行时Bug。

分享到:

如果这篇文章对你有帮助,欢迎请作者喝杯咖啡 ☕

加载评论中...