- 文档
- 教程
【免费下载链接】TypeScript
TypeScript 使用手册(中文版)翻译。http://www.typescriptlang.org
本文基于 TypeScript 使用手册(中文版)索引访问类型 章节展开,属于「用现有类型创建新类型」手册系列的核心一环。索引访问类型(Indexed Access Types)允许你像读取对象属性一样从类型中提取另一个类型,是编写泛型工具类型、类型守卫与 API 类型推导时最常用的类型操作符之一。读完本文,你将掌握
T[K]语法的基础规则、与keyof/typeof的组合用法、数组元素类型的提取技巧,以及它在下标约束、条件类型与映射类型中的实战价值。
什么是索引访问类型
在 JavaScript 中,我们可以通过obj["key"]在值层面读取对象的属性值。TypeScript 的类型系统沿用了这一直觉:我们可以通过Type["key"]在类型层面,从另一个类型上查找并提取特定属性的类型。这种语法被称为索引访问类型。
type Person = { age: number; name: string; alive: boolean }; type Age = Person["age"]; // ^? type Age = number这里Person["age"]的结果类型是number——它直接取出了Person中age属性的声明类型,而非属性本身。索引访问的结果永远是一个类型,因此它可以像其他任何类型一样被赋值给类型别名、作为泛型参数传递,或参与后续的类型运算。
在 TypeScript 官方文档结构 中,索引访问类型被明确归类为"用现有类型创建新类型"的七种类型操作符之一,与其并列的还有 泛型、keyof类型操作符、typeof类型操作符、条件类型、映射类型 与模板字面量类型。它的定位是"使用Type['a']语法访问类型的子集"。
索引键的多样性:联合类型、keyof与任意类型
索引访问的方括号内本身就是一种类型,而不仅仅是一个字面量键名。这意味着你可以把联合类型、keyof的结果,甚至另一个类型别名直接放进方括号里:
type Person = { age: number; name: string; alive: boolean }; // ---cut--- type I1 = Person["age" | "name"]; // ^? type I1 = string | number type I2 = Person[keyof Person]; // ^? type I2 = string | number | boolean type AliveOrName = "alive" | "name"; type I3 = Person[AliveOrName]; // ^? type I3 = string | boolean逐个拆解这三个示例:
Person["age" | "name"]:索引键是联合类型"age" | "name",结果类型是对应属性类型的联合string | number。索引访问对联合键天然"分配"(distribute),等价于Person["age"] | Person["name"]。Person[keyof Person]:keyof Person会生成Person所有键的字面量联合类型(即"age" | "name" | "alive"),因此结果等同于把Person的所有属性类型联合起来,得到string | number | boolean。这正是"索引类型查询 + 索引访问"的标准组合拳。Person[AliveOrName]:证明索引键可以是任意类型别名——这里AliveOrName是预先定义好的字符串字面量联合类型,与直接书写Person["alive" | "name"]完全等价。
值得注意的是,keyof在配合索引签名时的行为会对结果产生影响。根据 keyof 类型操作符 的说明:如果类型具有string或number索引签名,keyof将返回相应的类型;且由于 JavaScript 对象的键总是被强制转换为字符串,obj[0]总是等同于obj["0"],因此带字符串索引签名的类型其keyof结果是string | number(而非仅string)。这一细节在 高级类型 章节中有同样明确的表述。
索引不存在的属性:编译期报错
索引访问类型在编译期受到严格校验。如果你尝试索引一个类型上不存在的属性,TypeScript 会立即报错:
// @errors: 2339 type Person = { age: number; name: string; alive: boolean }; // ---cut--- type I1 = Person["alve"];这里的"alve"是"alive"的拼写错误,Person上并不存在该属性,因此编译器抛出错误2339:Property 'alve' does not exist on type 'Person'(属性 'alve' 在类型 'Person' 上不存在)。这一特性使得索引访问类型自带"拼写安全网":任何键名错误、属性增删导致的引用失效,都会在类型检查阶段被捕获,而不是在运行时静默地返回undefined。
错误 2339 同样是对象属性访问(person.alve)在 TypeScript 中报错的标准错误码,说明索引访问类型在类型层面的检查强度与普通属性访问保持一致。
用number索引提取数组元素类型
索引访问类型最实用的场景之一,是使用number作为索引键来获取数组(或元组)的元素类型。数组类型的number索引对应其元素类型,因此T[number]可以提取出元素类型。把它与typeof类型操作符结合,就能方便地捕获数组字面量的元素类型:
const MyArray = [ { name: "Alice", age: 15 }, { name: "Bob", age: 23 }, { name: "Eve", age: 38 }, ]; type Person = typeof MyArray[number]; // ^? type Person = { name: string; age: number } type Age = typeof MyArray[number]["age"]; // ^? type Age = number // 或者 type Age2 = Person["age"]; // ^? type Age2 = number这里发生了两层运算:
typeof MyArray得到MyArray这个值所对应的类型——一个由三个对象组成的数组类型(严格来说是元组[{...}, {...}, {...}]或数组,取决于上下文推断)。[number]索引访问提取出数组的元素类型,即{ name: string; age: number },这与 typeof 类型操作符 中强调的"值"与"类型"是两个不同领域的理念一脉相承:typeof负责把值映射到类型,索引访问负责在类型内部继续取子集。
typeof MyArray[number]["age"]展示了索引访问的链式写法:先取元素类型,再取其中age属性的类型。最后的Age2 = Person["age"]说明:一旦把元素类型提取出来保存为类型别名Person,后续就可以直接对它继续索引,两种写法结果一致。
在条件类型章节中,T[number]被用于一个非常经典的Flatten工具类型:
type Flatten<T> = T extends any[] ? T[number] : T; // 提取出元素类型 type Str = Flatten<string[]>; // 等价于 string // 保持类型不变 type Num = Flatten<number>; // 等价于 number当Flatten接收到数组类型时,它使用number进行索引访问来提取出string[]的元素类型;否则直接返回原类型。这也是索引访问类型在"类型级编程"中承担核心工作的典型例证。
只能使用类型索引:const与类型别名的区别
索引访问的方括号内只能出现类型,不能出现值。因此,不能用const声明的变量作为索引键:
// @errors: 2538 2749 type Person = { age: number; name: string; alive: boolean }; // ---cut--- const key = "age"; type Age = Person[key];这段代码会同时触发两个错误:
- 错误 2538:Type
'string'cannot be used as an index type(类型 'string' 不能用作索引类型)。因为const key虽然值为"age",但被推断为广义的string类型,而Person没有字符串索引签名,无法用任意string去索引。 - 错误 2749:
'key'refers to a value, but is being used as a type here('key' 引用的是一个值,但在这里被用作类型)。
这正是 typeof 类型操作符 中反复强调的核心区别——值(value)和类型(type)不是同一个东西。索引访问是在类型层面进行的操作,所以它要求方括号内是类型层面的实体。
正确的重构方式是使用类型别名来保存键名——把"age"声明为一个类型而非值:
type Person = { age: number; name: string; alive: boolean }; // ---cut--- type key = "age"; type Age = Person[key];这里type key = "age"定义的是字符串字面量类型"age"(而不是广义string),因此Person[key]可以成功解析为number。这一做法将"键名"提升为可复用的类型抽象,当键名在多个地方被引用时,只需修改一处类型别名即可完成重构,与文档中"使用类型别名来进行类似的重构"的表述一致。
实战进阶:K extends keyof T约束下的T[K]
索引访问类型真正的威力,体现在与泛型约束的组合上。高级类型 章节给出了两个经典的工具函数,它们完整展示了T[K]在泛型场景下的用法:
function pluck<T, K extends keyof T>(o: T, propertyNames: K[]): T[K][] { return propertyNames.map((n) => o[n]); } function getProperty<T, K extends keyof T>(o: T, propertyName: K): T[K] { return o[propertyName]; // o[propertyName] 的类型是 T[K] }关键点在于:
K extends keyof T约束:K被限制为T的键的字面量联合类型,这保证传入的propertyName一定是对T合法的键,从而让T[K]的索引访问在编译期就是安全的。T[K]是"动态"的:getProperty的返回值类型T[K]会随着调用时传入的K具体化。当K为"name"时返回string,当K为"age"时返回number——编译器会实例化键的真实类型,返回值类型随之改变。- 类型语法与表达式语法对称:正如文档所述,"类型语法反映了表达式语法",
person['name']具有类型Person['name']。这种对称性让开发者可以把运行时的索引访问经验直接迁移到类型层面。
同一章节还说明了索引访问与字符串索引签名的交互:索引签名的参数类型必须为number或string;如果一个类型带有字符串索引签名,那么keyof T会是string | number(因为在 JavaScript 中既可以用字符串object['42']也可以用数字object[42]访问对象属性),而T[string]则得到索引签名的值类型。
在映射类型与条件类型中的联动
索引访问类型不是孤立的语法,它与手册其他类型操作符的组合构成了 TypeScript 类型级编程的主力工具:
映射类型中的Type[Property]。映射类型 在遍历键时通过Type[Property]读取每个属性的原始类型,例如:
type OptionsFlags<Type> = { [Property in keyof Type]: boolean; }; type CreateMutable<Type> = { -readonly [Property in keyof Type]: Type[Property]; };CreateMutable中Type[Property]正是索引访问类型:它把每个属性的原类型原样保留,同时通过-readonly修饰符移除只读属性。没有索引访问,映射类型将无法"透传"属性类型。
条件类型中的索引访问。除了前文Flatten<T> = T extends any[] ? T[number] : T之外,条件类型 中还有一个利用索引访问进行约束检查的示例:
type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;在条件类型的 true 分支中,TypeScript 知道T必然有message属性,因此T["message"]的索引访问是安全的;如果传入的类型没有message属性,则落入never分支。这里索引访问与条件类型的类型收窄(narrowing)能力完美配合。
版本演进:TS 2.7 起对索引访问约束的更严格检查
索引访问类型的校验强度并非一成不变。破坏性变更(Breaking Changes) 中记录了 TypeScript 2.7 对索引访问泛型类型约束的收紧:在 2.7.1 之前,仅当类型具有索引签名时才计算索引访问类型的约束,否则约束是any,这会允许无效赋值通过检查;2.7.1 起编译器会将约束计算为此处所有可能属性的并集,从而更早地暴露错误。这一改动意味着依赖T[K]的泛型代码在升级到 TS 2.7+ 时可能需要更明确的extends约束——这也是现代 TypeScript 代码中普遍使用K extends keyof T约束的由来之一。
小结
索引访问类型Type[K]是 TypeScript 类型系统中"按属性取类型"的基础能力,可以总结为四条核心规则:
- 索引键必须是类型:可以是字面量类型、联合类型、
keyof结果或类型别名,但不能是const变量等值实体(错误 2538/2749)。 - 索引不存在属性会报错:错误 2339 在编译期拦截拼写错误与失效引用。
T[number]提取数组元素类型:与typeof结合可轻松从数组字面量反推元素类型。- 与泛型、条件类型、映射类型组合:
K extends keyof T约束下的T[K]是getProperty、pluck、Flatten、MessageOf等工具函数/类型的地基。
如果你想继续深入,推荐按顺序阅读本系列的其他章节:keyof 类型操作符、typeof 类型操作符、条件类型 与映射类型,并可在高级类型中查看T[K]在真实工具函数中的完整用法。
- 文档
- 教程
【免费下载链接】TypeScript
TypeScript 使用手册(中文版)翻译。http://www.typescriptlang.org
相关推荐
TypeScript 类型索引(Type Indexing)实战指南:从 Index Signature 到 Indexed Access Types
TypeScript 类型索引(Type Indexing)实战指南:从 Index Signature 到 Indexed Access Types 类型索引
文档教程Flow Indexed Access Types(索引访问类型)完全指南:用 `T[K]` 从对象、数组与元组类型中提取属性类型
Flow Indexed Access Types(索引访问类型)完全指南:用 T K 从对象、数组与元组类型中提取属性类型 本指南系统讲解 Flow 的 In
开发工具静态分析代码质量Flow 索引访问类型(Indexed Access Types)实战:从 `ApiResponse['user']['profile']` 看嵌套类型复用
Flow 索引访问类型(Indexed Access Types)实战:从 ApiResponse 'user' 'profile' 看嵌套类型复用 Flow(
开发工具静态分析代码质量
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考