Coding Hao

TypeScript 中的 索引访问类型

2024年7月2日 (1年前)

假设我们有一个用户管理系统,用户的信息通过以下格式存储:

interface User {
  name: string;
  phone: number;
  address: {
    country: string;
    province: string;
    city: string;
    postalCode: number;
  }
}

这时,我们想获取用户 address 的类型,最直接的办法是将address抽取为单独的类型。

interface Address {
  country: string;
  province: string;
  city: string;
  postalCode: number;
}
interface User {
  name: string;
  phone: number;
  address: Address;
}

这段代码如果是我们自己业务可控的当然没问题,但如果它来源于第三方,我们没法直接修改代码怎么办呢?

这时候我们可以借助 ts 的索引访问类型(Indexed Access Types)来查找属性值的类型。

索引访问类型形如 T[K],有点类似 js 对象的属性访问。需要注意的是这里的 P 是类型(对象类型、数组类型或元组类型)而不是值,否则会报错。 Key 是一个属性名(字符串或数字)或索引签名。

const address = "address";
// 报错
// 'address' refers to a value, but is being used as a type here. Did you mean 'typeof address'?
type Address = User[address];
// ok,'address'是User类型的property,是一个字面量类型
type Address = User['address'];

以上通过特定的字面量属性获取类型是索引访问类型的最基本用法,下面介绍一些高级用法。

联合类型作为索引访问

可以使用联合类型作为索引,获取多个属性值的联合类型。比如:

interface User {
  name: string;
  phone: number;
  address: {
    country: string;
    province: string;
    city: string;
    postalCode: number;
  }
}
type NameOrPhone = 'name' | 'phone';
type Username1 = User[NameOrPhone]; // string | number
type Username2 = User['name' | 'phone']; // string | number

结合 keyof 操作符,可以获取对象所有属性的联合类型:

type Username = User[keyof User];
// 等价于
type Username = string | number | {
  country: string;
  province: string;
  city: string;
  postalCode: number;
}

深度访问

还是上面 User 的例子,不同的系统设计,address.postalCode可能是 number 也有可能是 string 类型。如果我们想获取它的类型怎么办呢?类似 js 对象的属性访问,索引访问类型也能一层一层的深度访问。

interface User {
  name: string;
  phone: number;
  address: {
    country: string;
    province: string;
    city: string;
    postalCode: number;
  }
}
type Address = User['address']['postalCode']; // number

T[string]和 T[number]

索引访问不一定要使用明确的字面量类型,它还允许使用 T[string]或 T[number]这样的特殊形式。 对于对象类型来说,如果定义了 index 签名,可以通过 T[string]获取属性值的所有类型的联合类型。

从数组类型中提取元素类型:

type StringArray = string[];
// 提取数组元素的类型
type ElementType = StringArray[number]; // string
  • number:表示数组的索引类型,用于提取数组元素的类型。

从元组类型中提取元素类型:

type Tuple = [string, number, boolean];
// 提取第一个元素的类型
type FirstElement = Tuple[0]; // string
// 提取第二个元素的类型
type SecondElement = Tuple[1]; // number

使用索引签名提取类型: 如果对象类型使用了索引签名,你可以通过索引访问类型提取索引签名的值类型。

type Dictionary = {
  [key: string]: number;
};
// 提取索引签名的值类型
type ValueType = Dictionary[string]; // number

结合 keyof 操作符:

type Person = {
  name: string;
  age: number;
  isActive: boolean;
};
// 获取所有键的联合类型
type Keys = keyof Person; // "name" | "age" | "isActive"
// 提取所有属性类型的联合类型
type ValueTypes = Person[Keys]; // string | number | boolean

总结

索引访问类型在以下场景中非常有用:

  • 动态提取属性类型:根据属性名动态获取类型。

  • 类型安全的访问:在泛型或工具类型中使用,确保类型安全。

  • 简化复杂类型操作:减少重复代码,提高代码可读性。