Skip to content
🔥 更新时间:

TypeScript 类型操作

在掌握了 TypeScript 的基础类型后,我们需要学习如何操作和转换这些类型。这些类型操作是类型体操的核心技术。

类型操作的基本概念

类型操作允许我们基于现有类型创建新的类型。TypeScript 提供了多种方式来操作类型,包括:

  • 映射类型
  • 条件类型
  • 索引访问类型
  • 类型推断(infer)
  • 模板字面量类型

映射类型

映射类型允许你基于旧类型创建新类型,通过遍历旧类型的所有属性并依照一定规则进行转换。

基本语法

typescript
type MappedType<T> = {
  [P in keyof T]: TransformedType
}

常见映射类型示例

将对象所有属性设为只读

typescript
type Readonly<T> = {
  readonly [P in keyof T]: T[P]
}

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

type ReadonlyUser = Readonly<User>;
// 等价于
type ReadonlyUser = {
  readonly id: number;
  readonly name: string;
}

将对象所有属性设为可选

typescript
type Partial<T> = {
  [P in keyof T]?: T[P]
}

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

type PartialUser = Partial<User>;
// 等价于
type PartialUser = {
  id?: number;
  name?: string;
}

从对象中选择一部分属性

typescript
type Pick<T, K extends keyof T> = {
  [P in K]: T[P]
}

interface User {
  id: number;
  name: string;
  email: string;
}

type UserBasic = Pick<User, 'id' | 'name'>;
// 等价于
type UserBasic = {
  id: number;
  name: string;
}

修饰符

映射类型可以添加或删除特定修饰符:

typescript
// 添加 readonly 修饰符
type ToReadonly<T> = {
  readonly [P in keyof T]: T[P]
}

// 移除 readonly 修饰符
type FromReadonly<T> = {
  -readonly [P in keyof T]: T[P]
}

// 添加可选修饰符
type ToOptional<T> = {
  [P in keyof T]?: T[P]
}

// 移除可选修饰符
type FromOptional<T> = {
  [P in keyof T]-?: T[P]
}

条件类型

条件类型允许你根据一个条件来选择不同的类型。

基本语法

typescript
type ConditionalType<T> = T extends Condition ? TrueType : FalseType

条件类型示例

从联合类型中排除某些类型

typescript
type Exclude<T, U> = T extends U ? never : T

type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<string | number | boolean, string>; // number | boolean

提取匹配的类型

typescript
type Extract<T, U> = T extends U ? T : never

type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T1 = Extract<string | number | boolean, boolean | number>; // number | boolean

获取函数返回类型

typescript
type ReturnType<T extends (...args: any[]) => any> =
  T extends (...args: any[]) => infer R ? R : any

function foo() { return { a: 1, b: 2 }; }
type FooReturn = ReturnType<typeof foo>; // { a: number, b: number }

类型推断 (infer)

infer 关键字允许你在条件类型中推断类型,并在 true 分支中引用该类型。

infer 基本用法

typescript
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never

// 例子
function foo() { return 42; }
type FooReturnType = GetReturnType<typeof foo>; // number

推断数组元素类型

typescript
type ArrayElementType<T> = T extends (infer E)[] ? E : never

type T1 = ArrayElementType<number[]>; // number
type T2 = ArrayElementType<string[]>; // string
type T3 = ArrayElementType<[number, string]>; // number | string

推断 Promise 值类型

typescript
type PromiseValueType<T> = T extends Promise<infer V> ? V : never

type T1 = PromiseValueType<Promise<string>>; // string
type T2 = PromiseValueType<Promise<number>>; // number

索引访问类型

索引访问类型允许你通过索引来访问其他类型的特定部分。

基本语法

typescript
type PropertyType<T, K extends keyof T> = T[K]

示例

typescript
interface Person {
  name: string;
  age: number;
  address: {
    city: string;
    country: string;
  }
}

type PersonName = Person['name']; // string
type PersonNameOrAge = Person['name' | 'age']; // string | number
type PersonAddress = Person['address']; // { city: string; country: string; }
type AddressProperty = Person['address']['city']; // string

模板字面量类型

TypeScript 4.1 引入了模板字面量类型,允许你通过模板字符串的方式构建新的字符串字面量类型。

基本语法

typescript
type TemplateLiteralType<T extends string, U extends string> = `${T}${U}`

示例

typescript
type Direction = 'top' | 'right' | 'bottom' | 'left';
type Position = 'start' | 'center' | 'end';

type EdgePosition = `${Direction}-${Position}`;
// 'top-start' | 'top-center' | 'top-end' | ... 其他组合

与映射类型结合使用

typescript
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;
// }

高级类型工具

掌握上面的基本类型操作后,我们可以创建更复杂的类型工具:

DeepReadonly

递归地将对象所有属性设为只读:

typescript
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object
    ? T[P] extends Function
      ? T[P]
      : DeepReadonly<T[P]>
    : T[P]
}

DeepPartial

递归地将对象所有属性设为可选:

typescript
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object
    ? DeepPartial<T[P]>
    : T[P]
}

Flatten

扁平化嵌套对象的类型,使用路径作为键:

typescript
type Flatten<T extends object, Prefix extends string = ''> = {
  [K in keyof T as T[K] extends object
    ? never
    : `${Prefix}${string & K}`]: T[K]
} & (
  {
    [K in keyof T as T[K] extends object
      ? `${Prefix}${string & K}`
      : never]: T[K] extends object ? Flatten<T[K], `${Prefix}${string & K}.`> : never
  }
)

掌握了这些核心类型操作,你就可以开始挑战本站上的类型体操挑战,继续深入学习高级技巧

Contributors

The avatar of contributor named as fengzebing fengzebing

Changelog

本站访客数 人次 本站总访问量