本文是TypeScript中文手册学习笔记
1. 简介
TypeScript的核心原则之一是对值所具有的结构进行类型检查。 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。
在以下代码中,printLabel函数的参数是一个封装起来的labelledObj对象,并且这个对象有一个名字为label的string类型的属性,值得一提的是,labelledObj可以有很多属性,但是编译器只会检查必须的属性(本例中为label属性)是否存在与类型是否匹配。
function printLabel(labelledObj: { label: string }) {
console.log(labelledObj.label);
}
let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);
但是TypeScript不会像上述这样宽松,将上述例子用接口重写,我们定义一个叫LabelledValue的接口,相当于给属性取了一个名字,但是我们不能说labelledObj这个传递给printLabel的对象实现了LabelledValue接口,而是只关注值的外形,并且和顺序无关。
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
2. 可选属性和只读属性
有些属性可能不一定存在,可以通过在属性名字后加一个?的形式表示其可选,这种可选属性在option bags模式中十分常用。
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): {color: string; area: number} {
let newSquare = {color: "white", area: 100};
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
let mySquare = createSquare({color: "black"});
对智能在对象创建时修改的属性可以在属性名前加一个readonly来指定其只读属性。
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!
TypeScript具有ReadonlyArray类型,它与Array相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改:
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
最后一行就算把整个ReadonlyArray赋值给一个普通数组也不可以,但是可以用类型断言重写:
a = ro as number[];
3. 额外的属性检查
接口可以实现将{ size: number; label: string; }传入到仅期望得到{ label: string; }的函数里;可选属性又可以指定可以不被传入的属性。但是这二者不能结合起来。
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
// ...
}
// error: 'colour' not expected in type 'SquareConfig'
let mySquare = createSquare({ colour: "red", width: 100 });
在js里当然会报错,因为colour参数拼写错误了,但ts里,有人可能会认为width已然兼容,因为可选属性,color可以不被传入,又因为接口,所以传入多余的colour属性也没问题,但是实际上ts会认为这段代码可能存在bug,如果一个对象字面量存在任何“目标类型”不包含的属性时,你会得到一个错误。
绕开这些检查非常简单。 最简便的方法是使用类型断言:
let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);
然而,最佳的方式是能够添加一个字符串索引签名,前提是你能够确定这个对象可能具有某些做为特殊用途使用的额外属性。 如果SquareConfig带有上面定义的类型的color和width属性,并且还会带有任意数量的其它属性,那么我们可以这样定义它:
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}
4. 函数类型
接口除了用来描述有属性的普通对象(所谓给对象起名字),还可以用来描述函数类型。
interface SearchFunc {
(source: string, subString: string): boolean;
}
这里我们定义了一个接口的调用签名,冒号前是参数列表后是返回值。
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
let result = source.search(subString);
return result > -1;
}
// 名字不匹配
let mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean {
let result = src.search(sub);
return result > -1;
}
定义一个函数类型的变量,函数的参数会逐个进行检查,名字不一定匹配,只要求对应位置上的参数类型是兼容的。 如果你不想指定类型,TypeScript的类型系统会推断出参数类型,因为函数直接赋值给了SearchFunc类型变量。 函数的返回值类型是通过其返回值推断出来的(此例是false和true)。 如果让这个函数返回数字或字符串,类型检查器会警告我们函数的返回值类型与SearchFunc接口中的定义不匹配。
// 不指定类型
let mySearch: SearchFunc;
mySearch = function(src, sub) {
let result = src.search(sub);
return result > -1;
}
5. 可索引的类型
也可以使用接口描述可以通过索引得到的类型。
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
我们定义了StringArray接口,它具有索引签名。 这个索引签名表示了当用number去索引StringArray时会得到string类型的返回值。
共有支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用number来索引时,JavaScript会将它转换成string然后再去索引对象。 也就是说用100(一个number)去索引等同于使用"100"(一个string)去索引,因此两者需要保持一致。
class Animal {
name: string;
}
class Dog extends Animal {
breed: string;
}
// 错误:使用'string'索引,有时会得到Animal!
interface NotOkay {
[x: number]: Animal;
[x: string]: Dog;
}
字符串索引声明了obj.property和obj[“property”]两种形式都可以。