# 索引类型 (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 的源头,推荐阅读

# 索引访问类型 (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"];


  

# 知识点