官网
与使用接口描述函数类型差不多,我们也可以描述那些能够“通过索引得到”的类型,比如a[10]
或ageMap["daniel"]
。 可索引类型具有一个 索引签名
,它描述了对象索引的类型
,还有相应的索引返回值类型
。 让我们看一个例子:
interface StringArray {
[index: number]: string; //索引签名
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
上面例子里,我们定义了StringArray
接口,它具有索引签名。 这个索引签名表示了当用 number
去索引StringArray
时会得到string
类型的返回值。
TypeScript支持两种索引签名:字符串
和数字
。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用 number
来索引时,JavaScript会将它转换成string
然后再去索引对象。 也就是说用 100(一个number)去索引等同于使用"100"(一个string)去索引,因此两者需要保持一致。
class Animal {
name: string;
}
class Dog extends Animal {
breed: string;
}
// 错误:使用数值型的字符串索引,有时会得到完全不同的Animal!
interface NotOkay {
[x: number]: Animal;
[x: string]: Dog;
}
//编译OK
interface Okay {
[x: number]: Dog; //Dog是Animal子类
[x: string]: Animal;
}
字符串索引签名能够很好的描述dictionary
模式,并且它们也会确保所有属性与其返回值类型相匹配。 因为字符串索引声明了 obj.property
和obj["property"]
两种形式都可以。 下面的例子里, name
的类型与字符串索引类型不匹配,所以类型检查器给出一个错误提示:
interface NumberDictionary {
[index: string]: number;
length: number; // 可以,length是number类型
name: string // 错误,`name`的类型与索引类型返回值的类型不匹配
}
最后,你可以将索引签名设置为只读,这样就防止了给索引赋值:
interface ReadonlyStringArray {
readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!
你不能设置myArray[2]
,因为索引签名是只读的。
上文讲解可索引的类型语法及规则限制,我们来看下使用场景。
我们来看个例子:
我们传参的对象多了一个属性'colour'
,这个属性一个新属性。
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig) {
// ...
}
let mySquare = createSquare({ colour: "red", width: 100 }); //编译失败,注意此时的colour属性拼写非'color',故意模拟新增一个属性,而非拼写错误
会报错误 Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'
绕开这些检查非常简单。 最简便的方法是使用类型断言:
let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);
然而,最佳的方式是能够添加一个字符串索引签名,前提是你能够确定这个对象可能具有某些做为特殊用途使用的额外属性。 如果 SquareConfig带有上面定义的类型的color和width属性,并且还会带有任意数量的其它属性,那么我们可以这样定义它:
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any; //返回值是any,如果是确定值,那么注意color和width的返回值类型必须是这个确定值的子集
}
还有最后一种跳过这些检查的方式,这可能会让你感到惊讶,它就是将这个对象赋值给一个另一个变量: 因为 squareOptions不会经过额外属性检查,所以编译器不会报错。
let squareOptions = { colour: "red", width: 100 };
let mySquare = createSquare(squareOptions);
要留意,在像上面一样的简单代码里,你可能不应该去绕开这些检查。 对于包含方法和内部状态的复杂对象字面量来讲,你可能需要使用这些技巧,但是大部额外属性检查错误是真正的bug。 就是说你遇到了额外类型检查出的错误,比如“option bags”,你应该去审查一下你的类型声明。 在这里,如果支持传入
color
或colour
属性到createSquare
,你应该修改SquareConfig
定义来体现出这一点。