TypeScript 高级类型:从入门到精通

掌握 TypeScript 高级类型技巧,让类型系统成为你的开发利器

TypeScript 高级类型:从入门到精通

TypeScript 的类型系统远比 stringnumberboolean 强大得多。掌握高级类型技巧,你可以构建出既类型安全又灵活优雅的代码。本文将带你深入 TypeScript 类型系统的精髓。

类型推断的艺术

as const 断言

// 普通声明:类型被放宽
const config = {
  endpoint: '/api/users',
  method: 'GET'
};
// 类型: { endpoint: string; method: string }

// as const:类型被收窄为字面量
const config = {
  endpoint: '/api/users',
  method: 'GET'
} as const;
// 类型: { readonly endpoint: "/api/users"; readonly method: "GET" }

as const 在定义常量配置、枚举替代方案时非常有用:

const HttpMethods = ['GET', 'POST', 'PUT', 'DELETE'] as const;
type HttpMethod = typeof HttpMethods[number];
// 类型: "GET" | "POST" | "PUT" | "DELETE"

satisfies 操作符

TypeScript 4.9 引入的 satisfies 让你同时获得类型检查和精确推断:

type Colors = Record<string, string | number[]>;

// 使用类型注解:丢失具体信息
const colors: Colors = {
  red: '#ff0000',
  green: [0, 255, 0]
};
colors.red.toUpperCase(); // ❌ 错误:可能是 number[]

// 使用 satisfies:保留具体类型
const colors = {
  red: '#ff0000',
  green: [0, 255, 0]
} satisfies Colors;
colors.red.toUpperCase();   // ✅ 正确:知道是 string
colors.green.map(n => n);   // ✅ 正确:知道是 number[]

条件类型

条件类型是 TypeScript 类型编程的核心:

type IsString<T> = T extends string ? true : false;

type A = IsString<'hello'>;  // true
type B = IsString<42>;       // false

infer 关键字

infer 让你在条件类型中提取类型:

// 提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type Fn = () => Promise<string>;
type Result = ReturnType<Fn>;  // Promise<string>

// 提取 Promise 内部类型
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;

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

// 提取数组元素类型
type ElementType<T> = T extends (infer E)[] ? E : never;

type E = ElementType<string[]>;  // string

实用条件类型

// 提取对象中值为函数的键
type FunctionKeys<T> = {
  [K in keyof T]: T[K] extends Function ? K : never
}[keyof T];

interface User {
  name: string;
  age: number;
  greet(): void;
  update(data: Partial<User>): void;
}

type UserMethods = FunctionKeys<User>;  // "greet" | "update"

映射类型

映射类型让你基于现有类型创建新类型:

// 基础映射
type Readonly<T> = {
  readonly [K in keyof T]: T[K]
};

type Partial<T> = {
  [K in keyof T]?: T[K]
};

type Required<T> = {
  [K in keyof T]-?: T[K]  // -? 移除可选标记
};

键重映射(Key Remapping)

TypeScript 4.1 引入了 as 子句进行键重映射:

// 给所有键添加前缀
type Prefixed<T, P extends string> = {
  [K in keyof T as `${P}${Capitalize<string & K>}`]: T[K]
};

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

type PrefixedUser = Prefixed<User, 'user'>;
// { userName: string; userAge: number }

// 过滤特定类型的键
type OnlyStrings<T> = {
  [K in keyof T as T[K] extends string ? K : never]: T[K]
};

type StringProps = OnlyStrings<User>;
// { name: string }

Getter/Setter 生成器

type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};

type Setters<T> = {
  [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void
};

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

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

type PersonSetters = Setters<Person>;
// { setName: (value: string) => void; setAge: (value: number) => void }

模板字面量类型

TypeScript 4.1 带来了强大的模板字面量类型:

type EventName<T extends string> = `on${Capitalize<T>}`;

type ClickEvent = EventName<'click'>;  // "onClick"
type FocusEvent = EventName<'focus'>;  // "onFocus"

// 组合多个字面量
type Vertical = 'top' | 'bottom';
type Horizontal = 'left' | 'right';
type Position = `${Vertical}-${Horizontal}`;
// "top-left" | "top-right" | "bottom-left" | "bottom-right"

内置字符串操作类型

type Upper = Uppercase<'hello'>;      // "HELLO"
type Lower = Lowercase<'HELLO'>;      // "hello"
type Cap = Capitalize<'hello'>;       // "Hello"
type Uncap = Uncapitalize<'Hello'>;   // "hello"

实战:类型安全的事件系统

type EventMap = {
  click: { x: number; y: number };
  focus: { target: HTMLElement };
  submit: { data: FormData };
};

type EventHandler<T extends keyof EventMap> = (event: EventMap[T]) => void;

type EventHandlers = {
  [K in keyof EventMap as `on${Capitalize<K>}`]: EventHandler<K>
};

// 结果:
// {
//   onClick: (event: { x: number; y: number }) => void;
//   onFocus: (event: { target: HTMLElement }) => void;
//   onSubmit: (event: { data: FormData }) => void;
// }

递归类型

TypeScript 支持递归类型定义:

// 深度只读
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? DeepReadonly<T[K]>
    : T[K]
};

// 深度可选
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object
    ? DeepPartial<T[K]>
    : T[K]
};

// 展平嵌套对象路径
type Paths<T, D extends number = 10> = [D] extends [never]
  ? never
  : T extends object
    ? {
        [K in keyof T]-?: K extends string | number
          ? `${K}` | `${K}.${Paths<T[K], Prev[D]>}`
          : never
      }[keyof T]
    : never;

type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

interface Config {
  server: {
    host: string;
    port: number;
  };
  database: {
    connection: {
      url: string;
    };
  };
}

type ConfigPaths = Paths<Config>;
// "server" | "server.host" | "server.port" | "database" | "database.connection" | "database.connection.url"

类型体操实战

1. 元组操作

// 获取元组第一个元素
type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never;

// 获取元组最后一个元素
type Last<T extends any[]> = T extends [...any[], infer L] ? L : never;

// 移除第一个元素
type Shift<T extends any[]> = T extends [any, ...infer R] ? R : never;

// 移除最后一个元素
type Pop<T extends any[]> = T extends [...infer R, any] ? R : never;

// 反转元组
type Reverse<T extends any[]> = T extends [infer F, ...infer R]
  ? [...Reverse<R>, F]
  : [];

type Tuple = [1, 2, 3, 4, 5];
type FirstEl = First<Tuple>;     // 1
type LastEl = Last<Tuple>;       // 5
type Shifted = Shift<Tuple>;     // [2, 3, 4, 5]
type Popped = Pop<Tuple>;        // [1, 2, 3, 4]
type Reversed = Reverse<Tuple>;  // [5, 4, 3, 2, 1]

2. 联合类型转交叉类型

type UnionToIntersection<U> =
  (U extends any ? (x: U) => void : never) extends
  (x: infer I) => void ? I : never;

type Union = { a: string } | { b: number } | { c: boolean };
type Intersection = UnionToIntersection<Union>;
// { a: string } & { b: number } & { c: boolean }

3. 严格的对象类型

// 精确类型,不允许额外属性
type Exact<T, Shape> = T extends Shape
  ? Exclude<keyof T, keyof Shape> extends never
    ? T
    : never
  : never;

type User = { name: string; age: number };

function createUser<T extends User>(user: Exact<T, User>): User {
  return user;
}

createUser({ name: 'Alice', age: 30 });           // ✅
createUser({ name: 'Bob', age: 25, extra: 1 });   // ❌ 类型错误

实用工具类型

深度必需

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

可空类型处理

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

选择性 Pick 和 Omit

// 只选择特定类型的属性
type PickByType<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: T[K]
};

// 排除特定类型的属性
type OmitByType<T, U> = {
  [K in keyof T as T[K] extends U ? never : K]: T[K]
};

interface Mixed {
  name: string;
  age: number;
  active: boolean;
  email: string;
}

type StringProps = PickByType<Mixed, string>;
// { name: string; email: string }

type NonBooleanProps = OmitByType<Mixed, boolean>;
// { name: string; age: number; email: string }

类型断言函数

// 断言函数
function assertIsString(value: unknown): asserts value is string {
  if (typeof value !== 'string') {
    throw new Error('Not a string');
  }
}

function processValue(value: unknown) {
  assertIsString(value);
  // 此后 value 的类型是 string
  console.log(value.toUpperCase());
}

// 类型守卫
function isUser(obj: unknown): obj is User {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'name' in obj &&
    'age' in obj
  );
}

function greet(input: unknown) {
  if (isUser(input)) {
    // input 的类型是 User
    console.log(`Hello, ${input.name}!`);
  }
}

最佳实践

1. 避免过度使用 any

// ❌ 避免
function process(data: any): any {
  return data.value;
}

// ✅ 使用泛型
function process<T extends { value: unknown }>(data: T): T['value'] {
  return data.value;
}

2. 优先使用 unknown 而非 any

// ❌ any 允许任何操作
function dangerous(value: any) {
  value.foo.bar.baz();  // 不会报错,但运行时可能崩溃
}

// ✅ unknown 强制类型检查
function safe(value: unknown) {
  if (typeof value === 'object' && value !== null && 'foo' in value) {
    // 安全访问
  }
}

3. 利用 const 泛型

function createConfig<const T extends Record<string, unknown>>(config: T): T {
  return config;
}

const config = createConfig({
  api: '/api',
  timeout: 5000
});
// 类型: { readonly api: "/api"; readonly timeout: 5000 }

总结

TypeScript 高级类型是一把双刃剑:

场景建议
库/框架开发充分利用高级类型提供最佳 DX
业务代码适度使用,保持可读性
类型体操学习原理,实战中谨慎使用

关键收获

  1. as constsatisfies 提升类型精度
  2. 条件类型 + infer 是类型编程的核心
  3. 映射类型 + 键重映射实现类型转换
  4. 模板字面量类型处理字符串类型
  5. 递归类型处理嵌套结构

掌握这些高级类型技巧,你的 TypeScript 代码将更加类型安全、表达力更强。记住:类型系统是你的朋友,而非敌人


类型即文档,类型即测试,类型即安全网。让 TypeScript 的类型系统为你的代码保驾护航。