Skip to content

索引访问类型

概述

索引访问类型(Indexed Access Types)是 TypeScript 中用于访问对象类型属性类型或数组元素类型的语法。通过使用方括号 T[K] 语法,我们可以从现有类型中提取特定属性的类型,类似于 JavaScript 中通过索引访问对象属性,但作用于类型层面。索引访问类型让我们能够动态地获取类型的组成部分,是构建复杂类型工具的基础。

为什么需要索引访问类型

在没有索引访问类型的情况下,我们无法动态地获取类型的属性类型:

typescript
// 没有索引访问类型:无法动态获取属性类型
interface User {
  name: string;
  age: number;
  email: string;
}

// 需要手动定义每个属性的类型
type UserName = string;  // 手动指定
type UserAge = number;   // 手动指定

// 使用索引访问类型:动态获取属性类型
type UserName = User['name'];  // 类型:string(自动从 User 中提取)
type UserAge = User['age'];    // 类型:number(自动从 User 中提取)

索引访问类型让我们能够:

  1. 动态提取属性类型:从对象类型中提取特定属性的类型
  2. 访问嵌套属性:访问深层嵌套对象的属性类型
  3. 与 keyof 结合:创建灵活的类型工具
  4. 类型级编程:在类型层面实现数据访问逻辑

基本语法

基本索引访问

索引访问类型使用方括号语法 T[K],其中 T 是对象类型,K 是键的类型:

typescript
// 基本语法:T[K]
// T 是对象类型
// K 是键的类型(字符串字面量类型或 keyof T)

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

// 访问单个属性类型
type UserName = User['name'];   // 类型:string
type UserAge = User['age'];     // 类型:number
type UserEmail = User['email']; // 类型:string

索引访问的工作原理

索引访问类型通过键名从对象类型中提取对应属性的类型:

typescript
interface Config {
  host: string;
  port: number;
  ssl: boolean;
  database: {
    name: string;
    pool: number;
  };
}

// 访问基本属性
type Host = Config['host'];  // 类型:string
type Port = Config['port'];  // 类型:number
type Ssl = Config['ssl'];    // 类型:boolean

// 访问嵌套属性
type Database = Config['database'];        // 类型:{ name: string; pool: number }
type DatabaseName = Config['database']['name'];  // 类型:string

使用联合类型作为键

可以使用联合类型作为键,访问多个属性:

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

// 使用联合类型访问多个属性
type UserNameOrAge = User['name' | 'age'];
// 类型:string | number

// 访问所有属性(使用 keyof)
type AllUserProperties = User[keyof User];
// 类型:string | number | string
// 等价于:string | number

与 keyof 操作符结合

索引访问类型经常与 keyof 操作符结合使用,实现更灵活的类型操作:

基本结合使用

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

// 获取所有键的联合类型
type UserKeys = keyof User;
// 类型:'name' | 'age' | 'email'

// 使用 keyof 访问所有属性类型
type AllUserValues = User[keyof User];
// 类型:string | number | string
// 等价于:string | number

创建类型工具

结合 keyof 和索引访问类型,可以创建实用的类型工具:

typescript
// 获取对象类型的所有值类型
type ValueOf<T> = T[keyof T];

// 获取特定键的值类型
type ValueOfKey<T, K extends keyof T> = T[K];

// 使用示例
interface User {
  name: string;
  age: number;
  email: string;
}

type AllValues = ValueOf<User>;
// 类型:string | number

type NameValue = ValueOfKey<User, 'name'>;
// 类型:string

数组和元组的索引访问

索引访问类型也可以用于数组和元组类型:

数组类型

typescript
// 访问数组元素类型
type StringArray = string[];
type StringElement = StringArray[number];
// 类型:string

// 使用泛型
type ArrayElement<T> = T extends (infer U)[] ? U : never;

type NumberArray = number[];
type NumberElement = ArrayElement<NumberArray>;
// 类型:number

元组类型

typescript
// 访问元组的特定索引
type Tuple = [string, number, boolean];
type First = Tuple[0];   // 类型:string
type Second = Tuple[1];  // 类型:number
type Third = Tuple[2];   // 类型:boolean

// 访问元组的所有元素类型
type AllElements = Tuple[number];
// 类型:string | number | boolean

// 访问元组的长度(TypeScript 4.1+)
type Length = Tuple['length'];
// 类型:3

动态索引访问

typescript
// 使用数字字面量类型访问元组
type GetTupleElement<T extends readonly any[], K extends number> = T[K];

type Tuple = [string, number, boolean];
type First = GetTupleElement<Tuple, 0>;  // 类型:string
type Second = GetTupleElement<Tuple, 1>; // 类型:number

使用示例

示例 1:提取函数返回类型

typescript
// 从函数类型中提取返回类型
interface Api {
  getUser: (id: number) => { name: string; age: number };
  createUser: (data: { name: string }) => { id: number; name: string };
  deleteUser: (id: number) => void;
}

// 提取特定方法的返回类型
type GetUserReturn = ReturnType<Api['getUser']>;
// 类型:{ name: string; age: number }

type CreateUserReturn = ReturnType<Api['createUser']>;
// 类型:{ id: number; name: string }

type DeleteUserReturn = ReturnType<Api['deleteUser']>;
// 类型:void

// 提取所有方法的返回类型
type AllReturns = ReturnType<Api[keyof Api]>;
// 类型:{ name: string; age: number } | { id: number; name: string } | void

示例 2:类型安全的配置访问

typescript
// 定义配置类型
interface AppConfig {
  database: {
    host: string;
    port: number;
    name: string;
  };
  server: {
    port: number;
    timeout: number;
  };
  features: {
    auth: boolean;
    logging: boolean;
  };
}

// 创建类型安全的配置访问函数
function getConfigValue<T extends keyof AppConfig, K extends keyof AppConfig[T]>(
  section: T,
  key: K
): AppConfig[T][K] {
  // 实现省略
  return {} as AppConfig[T][K];
}

// 使用示例
const dbHost = getConfigValue('database', 'host');
// 类型:string

const serverPort = getConfigValue('server', 'port');
// 类型:number

const authEnabled = getConfigValue('features', 'auth');
// 类型:boolean

// ❌ 类型错误:'invalid' 不是有效的键
// const invalid = getConfigValue('database', 'invalid');

示例 3:API 响应类型提取

typescript
// 定义 API 响应类型
interface ApiResponses {
  '/users': { id: number; name: string; email: string }[];
  '/users/:id': { id: number; name: string; email: string };
  '/posts': { id: number; title: string; content: string }[];
  '/posts/:id': { id: number; title: string; content: string };
}

// 提取特定端点的响应类型
type UsersResponse = ApiResponses['/users'];
// 类型:{ id: number; name: string; email: string }[]

type UserResponse = ApiResponses['/users/:id'];
// 类型:{ id: number; name: string; email: string }

// 创建类型安全的 API 客户端
type ApiEndpoint = keyof ApiResponses;

function fetchApi<T extends ApiEndpoint>(endpoint: T): Promise<ApiResponses[T]> {
  // 实现省略
  return {} as Promise<ApiResponses[T]>;
}

// 使用示例
const users = await fetchApi('/users');
// 类型:Promise<{ id: number; name: string; email: string }[]>

const user = await fetchApi('/users/:id');
// 类型:Promise<{ id: number; name: string; email: string }>

示例 4:表单字段类型提取

typescript
// 定义表单类型
interface FormData {
  user: {
    name: string;
    email: string;
    age: number;
  };
  preferences: {
    theme: 'light' | 'dark';
    language: 'zh' | 'en';
  };
  settings: {
    notifications: boolean;
    autoSave: boolean;
  };
}

// 提取特定字段的类型
type UserName = FormData['user']['name'];
// 类型:string

type Theme = FormData['preferences']['theme'];
// 类型:'light' | 'dark'

type Notifications = FormData['settings']['notifications'];
// 类型:boolean

// 创建类型安全的表单验证函数
function validateField<T extends keyof FormData, K extends keyof FormData[T]>(
  section: T,
  field: K,
  value: FormData[T][K]
): boolean {
  // 验证逻辑
  return true;
}

// 使用示例
validateField('user', 'name', 'John');        // ✅ 正确
validateField('preferences', 'theme', 'dark'); // ✅ 正确
validateField('settings', 'notifications', true); // ✅ 正确

// ❌ 类型错误:类型不匹配
// validateField('user', 'name', 123);

示例 5:事件系统类型

typescript
// 定义事件类型
interface Events {
  click: { x: number; y: number };
  keydown: { key: string; code: number };
  resize: { width: number; height: number };
  submit: { formId: string; data: Record<string, any> };
}

// 提取特定事件的载荷类型
type ClickPayload = Events['click'];
// 类型:{ x: number; y: number }

type KeydownPayload = Events['keydown'];
// 类型:{ key: string; code: number }

// 创建类型安全的事件处理器
type EventName = keyof Events;

function on<T extends EventName>(
  event: T,
  handler: (payload: Events[T]) => void
): void {
  // 实现省略
}

// 使用示例
on('click', (payload) => {
  // payload 类型:{ x: number; y: number }
  console.log(payload.x, payload.y);
});

on('keydown', (payload) => {
  // payload 类型:{ key: string; code: number }
  console.log(payload.key, payload.code);
});

示例 6:深度嵌套类型访问

typescript
// 定义深度嵌套的类型
interface DeepStructure {
  level1: {
    level2: {
      level3: {
        value: string;
        count: number;
      };
      items: string[];
    };
    metadata: {
      created: Date;
      updated: Date;
    };
  };
}

// 访问深层嵌套的属性
type Level3Value = DeepStructure['level1']['level2']['level3']['value'];
// 类型:string

type Level3Count = DeepStructure['level1']['level2']['level3']['count'];
// 类型:number

type Items = DeepStructure['level1']['level2']['items'];
// 类型:string[]

type Created = DeepStructure['level1']['metadata']['created'];
// 类型:Date

// 创建类型安全的深度访问工具
type DeepAccess<T, K extends string> = K extends `${infer Key}.${infer Rest}`
  ? Key extends keyof T
    ? DeepAccess<T[Key], Rest>
    : never
  : K extends keyof T
  ? T[K]
  : never;

// 使用示例
type Value = DeepAccess<DeepStructure, 'level1.level2.level3.value'>;
// 类型:string

type Items2 = DeepAccess<DeepStructure, 'level1.level2.items'>;
// 类型:string[]

示例 7:工具类型实现

索引访问类型是许多内置工具类型的基础:

typescript
// Pick 工具类型(选择特定属性)
type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

// Omit 工具类型(排除特定属性)
type MyOmit<T, K extends keyof T> = {
  [P in keyof T as P extends K ? never : P]: T[P];
};

// Record 工具类型(创建键值对类型)
type MyRecord<K extends keyof any, T> = {
  [P in K]: T;
};

// 使用示例
interface User {
  name: string;
  age: number;
  email: string;
  phone: string;
}

type UserNameAndAge = MyPick<User, 'name' | 'age'>;
// 类型:{ name: string; age: number }

type UserWithoutEmail = MyOmit<User, 'email'>;
// 类型:{ name: string; age: number; phone: string }

type UserRecord = MyRecord<'id' | 'name', string>;
// 类型:{ id: string; name: string }

类型检查示例

常见错误

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

// ❌ 错误:使用不存在的键
// type Invalid = User['invalidKey'];  // 类型错误

// ❌ 错误:索引访问类型语法错误
// type Wrong = User.name;  // 应该使用方括号语法

// ❌ 错误:在非对象类型上使用索引访问
// type Wrong = string['length'];  // 不能直接访问原始类型的属性

正确写法

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

// ✅ 正确:基本索引访问
type UserName = User['name'];  // 类型:string

// ✅ 正确:使用联合类型作为键
type NameOrAge = User['name' | 'age'];  // 类型:string | number

// ✅ 正确:使用 keyof 访问所有属性
type AllValues = User[keyof User];  // 类型:string | number

// ✅ 正确:访问嵌套属性
interface Config {
  database: {
    host: string;
    port: number;
  };
}
type Host = Config['database']['host'];  // 类型:string

// ✅ 正确:数组和元组索引访问
type Array = string[];
type Element = Array[number];  // 类型:string

type Tuple = [string, number];
type First = Tuple[0];  // 类型:string

注意事项

提示

  • 索引访问类型使用方括号 T[K] 语法,类似于 JavaScript 中的属性访问
  • 可以使用字符串字面量类型、联合类型或 keyof T 作为键
  • 索引访问类型可以链式使用,访问嵌套对象的属性类型
  • keyof 操作符结合使用,可以创建强大的类型工具
  • 索引访问类型是构建工具类型(如 Pick、Omit)的基础

注意

  • 索引访问类型只能用于对象类型,不能直接用于原始类型
  • 使用不存在的键会导致类型错误
  • 访问可选属性时,结果类型可能包含 undefined
  • 对于联合类型的键,结果类型是所有对应属性类型的联合类型
  • 复杂的嵌套索引访问可能会影响类型检查性能

重要

  • 索引访问类型是 TypeScript 类型系统的核心特性,是理解高级类型的基础
  • 理解索引访问类型的工作原理对于掌握工具类型和类型级编程至关重要
  • 索引访问类型与 keyof、映射类型、条件类型结合使用,可以构建强大的类型系统
  • 索引访问类型不会影响运行时的性能,因为类型信息在编译时会被擦除

信息

索引访问类型的优势

  • 动态类型提取:从现有类型中动态提取属性类型
  • 类型安全:在编译时确保类型访问的正确性
  • 代码复用:避免重复定义类型,直接从现有类型中提取
  • 自文档化:类型定义本身就是文档

索引访问类型的应用场景

  • 工具类型实现(Pick、Omit、Record 等)
  • API 类型提取(响应类型、参数类型)
  • 配置对象类型访问
  • 表单字段类型提取
  • 事件系统类型定义
  • 深度嵌套类型访问

相关链接

基于 VitePress 构建