# 索引类型 (index type)
在之前 类型系统 已做过简单介绍,此处详细讨论其特性。
# 索引签名 (index signatures)
形如 [标识符:类型]: 类型 的形式称为索引签名,
它限制了对象属性可取的类型。注意标识符的类型只支持字符串和数值格式。
interface CustomArray {
length: number;
// 用于表示数组索引
[index: number]: any;
}
// 合法
let arr: CustomArray = [1, 2, 3, 4];
// 由于索引为数值,你甚至可以取小数,但不建议这么做
arr[0.1] = 1; // 合法
arr[0b1111] = 1; // 2 进制, 合法
arr[0o1111] = 1; // 8 进制, 合法
arr[0x11] = 1; // 16 进制, 合法
arr[1e3] = 1; // 科学计数法, 合法
// 报错, 索引为字符串非数值
arr = { a: 1 };
// 用于表示对象,也包括数组
interface CustomObject {
[index: string]: any;
}
// 合法
let objFoo: CustomObject = {
a: 1,
b: 'bar',
10: 'bar'
};
// 合法
objFoo = [1, 2, 3, 4];
注意高亮行,由于数字索引实际上也是字符串,所以限制标识符为字符串类型也可创建数组。
TIP
这也符合 JavaScript 的特性, a = [1,2],采用 a['1'],a[1.00] 均可访问到元素 a[1]
采用索引签名申明类型会限制,其他属性也必须属于索引签名的类型。
interface IndexFoo {
// 报错, foo 字段类型为对象,不属于 number 或 string 类型
foo: object;
[index: string]: string | number;
}
根据 TypeScript 规范 3.9.4 (opens new window) 索引特征在创建类型时只能出现一次
interface IndexInterface1 {
[index: string]: number;
// 报错,相同类型的索引签名最多只能出现一个
[index: string]: string;
}
interface IndexInterface2 {
[index: number]: number;
// 报错,相同类型的索引签名最多只能出现一个
[index: number]: string;
}
// 合法
interface IndexInterface3 {
[index: string]: number;
// 合法,数值和字符串索引同时出现
[index: number]: number;
}
# 索引类型查询( Index Type Query )
在 JavaScript 中我们常会采用 Object.keys 来提取对象的键名。索引类型查询提供了动态提取索引类型各键名对应子类型的功能
- 索引类型查询(index type queries) 采用 keyof 迭代对象
- 索引类型访问(Indexed access types) 采用 T[K] 获取索引的类型。
# keyof 查找类型(lookup type)
keyof 用于提取索引类型的键名,返回键名组成的联合类型。
interface Thing {
name: string;
width: number;
height: number;
inStock: boolean;
}
// 遍历对象类型 Thing 返回键名组成的联合类型
type KeyOfThing = keyof Thing;
let keyOfThing: KeyOfThing = 'name';
keyOfThing = 'width';
keyOfThing = 'height';
keyOfThing = 'inStock';
// 报错,值不属于 Thing 的键名
keyOfThing = 'a11';
结合索引类型访问 T[K] 可以提取对象部分属性组成新类型。
/**
* 该接口用于设置对象泛型 T 的属性,并要求属性值和对象泛型 T 对应的属性值类型相同
* T 为泛型,指代对象
* K extends keyof T 是对 K 的泛型约束,限定类型 K 为 T 的键名组成的联合类型
* T[K] 所赋值 value 必须和泛型 T 对应属性的值类型一致
*/
interface SetProperty<T, K extends keyof T> {
(obj: T, key: K, value: T[K]): void;
}
// 定义一个对象类型 Obj
interface Obj {
foo: string;
bar: number;
fooBar: string;
}
// 定义设置对象类型 Obj 属性的函数,且只能修改 'foo' 属性
let setObjProperty: SetProperty<Obj, 'foo'> = (obj, key: string, value) => {
obj[key] = value;
};
let o1: Obj = {
foo: 'foo',
bar: 12,
fooBar: 'fooBar'
};
// 合法
setObjProperty(o1, 'foo', 'demo');
// 报错, 值的类型非 string
setObjProperty(o1, 'foo', 12);
// 报错, 修改的属性非 foo
setObjProperty(o1, 'bar', 12);
keyof 也支持数组类型和索引类型提取。
interface Person {
name: string;
age: number;
location: string;
}
type K1 = keyof Person; // "name" | "age" | "location"
// keyof 会提取数组暴露的方法名
type K2 = keyof Person[]; // "length" | "push" | "pop" | "concat" | ...
// 由于此处 x 是泛指索引类型键名为 string 类型,keyof 提取的类型就是 string 而非 x
type K3 = keyof { [x: string]: Person }; // string补充数值和符号的枚举 number and symbol (opens new window)
交叉类型也可采用 keyof 此时会返回对应键名的联合类型。
interface A {
a: string;
}
interface B {
b: string;
}
// 返回 'a' | 'b'
type T1 = keyof (A & B);
交叉类型的其他介绍推荐 Improved keyof with intersection types (opens new window)
理解 keyof 的源头,推荐阅读
- TypeScript 仓库 issue Static types for dynamically named properties (opens new window)
- 官方 2.1 发布说明 keyof and Lookup Types (opens new window)
# 索引访问类型 (indexed access types)
除了通过 keyof 迭代索引类型。当采用 type 直接访问索引类型的某项元素时,也会直接提取类型。
interface Person {
name: string;
age: number;
location: string;
}
type P1 = Person["name"]; // string
type P2 = Person["name" | "age"]; // string | number
type P3 = string["charAt"]; // (pos: number) => string
type P4 = string[]["push"]; // (...items: string[]) => number
type P5 = string[][0]; // string当类型为联合类型时,会分别提取各项的属性组合为联合类型
type Events = {
type: 'click'
} | {
type: 'touch'
} | {
type: 'move'
}
// 返回 "click" | "touch" | "move"
type EventsName = Events['type']通过获取 keyof 实现对对象的安全访问
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key]; // Inferred type is T[K]
}
function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]) {
obj[key] = value;
}
let x = { foo: 10, bar: "hello!" };
let foo = getProperty(x, "foo"); // number
let bar = getProperty(x, "bar"); // string
let oops = getProperty(x, "wargarbl"); // Error! "wargarbl" is not "foo" | "bar"
setProperty(x, "foo", "string"); // Error!, string expected number# number
你也可以使用 number 指代任意键的索引类型。
const MyArray = [
{ name: "Alice", age: 15 },
{ name: "Bob", age: 23 },
{ name: "Eve", age: 45 },
];
// 该操作会自动综合 MyArray 的各项返回对应类型
type Person = typeof MyArray[number];
type Age = typeof MyArray[number]["age"];
type Age2 = Person["age"];