阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 索引访问类型

索引访问类型

作者:陈川 阅读数:8832人阅读 分类: TypeScript

索引访问类型的基本概念

索引访问类型(Indexed Access Types)允许通过索引获取另一个类型中的属性类型。语法形式为T[K],其中T是类型,K是能够作为索引的类型(通常是字符串字面量类型或联合类型)。这种类型查询方式类似于JavaScript中通过属性名访问对象属性值。

type Person = {
  name: string;
  age: number;
  address: {
    city: string;
    zipCode: string;
  };
};

type NameType = Person['name'];  // string
type AgeType = Person['age'];    // number
type AddressType = Person['address'];  // { city: string; zipCode: string; }

索引访问类型与联合类型

索引访问类型可以与联合类型结合使用,获取多个属性的联合类型。当索引是联合类型时,结果类型是对应属性类型的联合。

type PersonProps = Person['name' | 'age'];  // string | number
type AllValues = Person[keyof Person];  
// string | number | { city: string; zipCode: string; }

嵌套属性的访问

索引访问类型支持嵌套属性的访问,可以通过链式索引来获取深层属性的类型。

type CityType = Person['address']['city'];  // string
type ZipCodeType = Person['address']['zipCode'];  // string

数组和元组的索引访问

索引访问类型也适用于数组和元组类型。对于数组,可以使用number作为索引类型来获取元素类型;对于元组,可以使用数字字面量类型获取特定位置的类型。

type StringArray = string[];
type ElementType = StringArray[number];  // string

type Tuple = [string, number, boolean];
type First = Tuple[0];  // string
type Second = Tuple[1];  // number
type All = Tuple[number];  // string | number | boolean

与泛型结合使用

索引访问类型在泛型中特别有用,可以创建更灵活的类型操作。通过泛型参数约束,可以确保索引访问的安全性。

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

const person: Person = {
  name: 'Alice',
  age: 30,
  address: {
    city: 'New York',
    zipCode: '10001'
  }
};

const name = getProperty(person, 'name');  // string
const age = getProperty(person, 'age');    // number

动态属性访问

索引访问类型可以用于动态计算属性类型,这在处理复杂类型系统时非常有用。

type DynamicAccess<T, K extends string> = K extends keyof T ? T[K] : never;

type Result1 = DynamicAccess<Person, 'name'>;  // string
type Result2 = DynamicAccess<Person, 'email'>;  // never

与映射类型结合

索引访问类型常与映射类型一起使用,可以创建基于现有类型的新类型。

type ReadonlyPerson = {
  readonly [K in keyof Person]: Person[K];
};

// 等价于
type ReadonlyPerson = {
  readonly name: string;
  readonly age: number;
  readonly address: {
    city: string;
    zipCode: string;
  };
};

条件类型中的索引访问

在条件类型中使用索引访问类型可以实现更复杂的类型逻辑。

type NonFunctionPropertyNames<T> = {
  [K in keyof T]: T[K] extends Function ? never : K
}[keyof T];

type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;

interface MixedInterface {
  id: number;
  name: string;
  log: () => void;
  update: (data: any) => void;
}

type DataProps = NonFunctionProperties<MixedInterface>;
// { id: number; name: string }

索引访问类型的限制

索引访问类型有一些限制需要注意:

  1. 索引必须是已知的属性名或联合类型
  2. 不能访问不存在的属性
  3. 对于动态计算的属性名需要额外的类型保护
// 错误示例
type Invalid1 = Person['invalid'];  // 错误:类型"Person"上不存在属性"invalid"

// 需要类型保护
function safeAccess<T, K extends string>(obj: T, key: K): K extends keyof T ? T[K] : undefined {
  return (obj as any)[key];
}

实际应用场景

索引访问类型在实际开发中有多种应用场景:

  1. 类型安全的属性访问函数
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
  return items.map(item => item[key]);
}

const people: Person[] = [
  { name: 'Alice', age: 30, address: { city: 'NY', zipCode: '10001' } },
  { name: 'Bob', age: 25, address: { city: 'LA', zipCode: '90001' } }
];

const names = pluck(people, 'name');  // string[]
const ages = pluck(people, 'age');    // number[]
  1. 组件Props类型提取
interface ButtonProps {
  size: 'small' | 'medium' | 'large';
  variant: 'primary' | 'secondary';
  onClick: () => void;
  disabled?: boolean;
}

type ButtonSize = ButtonProps['size'];  // 'small' | 'medium' | 'large'
type ButtonVariant = ButtonProps['variant'];  // 'primary' | 'secondary'
  1. API响应类型处理
interface ApiResponse<T> {
  data: T;
  status: number;
  error?: string;
}

type ExtractData<T> = T extends ApiResponse<infer U> ? U : never;

type UserResponse = ApiResponse<{ id: string; name: string }>;
type UserData = UserResponse['data'];  // { id: string; name: string }

高级类型操作

索引访问类型可以用于构建更高级的类型操作工具:

  1. 深度属性访问
type DeepAccess<T, K extends string> = 
  K extends keyof T ? T[K] :
  K extends `${infer First}.${infer Rest}` ?
    First extends keyof T ? DeepAccess<T[First], Rest> :
    never :
  never;

type PersonCity = DeepAccess<Person, 'address.city'>;  // string
  1. 类型安全的路径访问
type PathImpl<T, K extends keyof T> =
  K extends string ?
    T[K] extends Record<string, any> ?
      `${K}.${PathImpl<T[K], keyof T[K]>}` | K :
      K :
    never;

type Path<T> = PathImpl<T, keyof T>;

type PersonPath = Path<Person>; 
// "name" | "age" | "address" | "address.city" | "address.zipCode"
  1. 基于路径的类型获取
type PathValue<T, P extends Path<T>> =
  P extends `${infer K}.${infer Rest}` ?
    K extends keyof T ? PathValue<T[K], Rest & Path<T[K]>> : never :
    P extends keyof T ? T[P] : never;

type ZipCodeType = PathValue<Person, 'address.zipCode'>;  // string

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌