参考
深入理解Typescript 索引签名
说说我对 TypeScript 索引签名的理解
一、JS中的情况
可以用字符串访问 JavaScript 中的对象(TypeScript 中也一样),用来保存对其他对象的引用。例如:
let foo: any = {};
foo['Hello'] = 'World';
console.log(foo['Hello']); // World
我们在键 Hello 下保存了一个字符串 World,除字符串外,它也可以保存任意的 JavaScript 对象,例如一个类的实例。
class Foo {
constructor(public message: string) {}
log() {
console.log(this.message);
}
}
let foo: any = {};
foo['Hello'] = new Foo('World');
foo['Hello'].log(); // World
当你传入一个其他对象至索引签名时,JavaScript 会在得到结果之前会先调用 .toString 方法:
let obj = {
toString() {
console.log('toString called');
return 'Hello';
}
};
let foo: any = {};
foo[obj] = 'World'; // toString called
console.log(foo[obj]); // toString called, World
console.log(foo['Hello']); // World
只要索引位置使用了 obj,toString 方法都将会被调用。
二、TypeScript 索引签名
JavaScript 在一个对象类型的索引签名上会隐式调用 toString 方法,而在 TypeScript 中,为防止初学者砸伤自己的脚(我总是看到 stackoverflow 上有很多 JavaScript 使用者都会这样。),它将会抛出一个错误。
const obj = {
toString() {
return 'Hello';
}
};
const foo: any = {};
// ERROR: 索引签名必须为 string, number....
foo[obj] = 'World';
// FIX: TypeScript 强制你必须明确这么做:
foo[obj.toString()] = 'World';
强制用户必须明确的写出 toString() 的原因是:在对象上默认执行的 toString 方法是有害的。例如 v8 引擎上总是会返回 [object Object]
1.声明一个索引签名
在上文中,我们通过使用 any 来让 TypeScript 允许我们可以做任意我们想做的事情。实际上,我们可以明确的指定索引签名。例如:假设你想确认存储在对象中任何内容都符合 { message: string } 的结构,你可以通过 [index: string]: { message: string } 来实现。
TypeScript 的索引签名必须是 string 或者 number。symbols 也是有效的,TypeScript 支持它。
const foo: {
[index: string]: { message: string };
} = {};
// 储存的东西必须符合结构
// ok
foo['a'] = { message: 'some message' };
// Error, 必须包含 `message`
foo['a'] = { messages: 'some message' };
// 读取时,也会有类型检查
// ok
foo['a'].message;
// Error: messages 不存在
foo['a'].messages;
索引签名的名称(如:{ [index: string]: { message: string } }
里的 index )除了可读性外,并没有任何意义。例如:如果有一个用户名,你可以使用 { username: string}: { message: string }
,这有利于下一个开发者理解你的代码。
number 类型的索引也支持:{ [count: number]: 'SomeOtherTypeYouWantToStoreEgRebate' }
2.所有成员都必须符合字符串的索引签名
当你声明一个索引签名时,所有明确的成员都必须符合索引签名:
// ok
interface Foo {
[key: string]: number;
x: number;
y: number;
}
// Error
interface Bar {
[key: string]: number;
x: number;
y: string; // Error: y 属性必须为 number 类型
}
这可以给你提供安全性,任何以字符串的访问都能得到相同结果。
interface Foo {
[key: string]: number;
x: number;
}
let foo: Foo = {
x: 1,
y: 2
};
// 直接
foo['x']; // number
// 间接
const x = 'x';
foo[x]; // number
3.使用一组有限的字符串字面量
TIPS:有点复杂
type Index = 'a' | 'b' | 'c';
type FromIndex = { [k in Index]?: number };
const good: FromIndex = { b: 1, c: 2 };
// Error:
// `{ b: 1, c: 2, d: 3 }` 不能分配给 'FromIndex'
// 对象字面量只能指定已知类型,'d' 不存在 'FromIndex' 类型上
const bad: FromIndex = { b: 1, c: 2, d: 3 };