TS: 接口的奥妙
1. 接口基础语法
interface Person{
name : string
}
function sayhelloto(person : Person){
console.log('hello,' + person.name);
}
let x = {
name : 'yivi',
age : 20
}
sayhelloto(x); // hello,yivi
类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对应的就能够被识别。
2. 可选属性
- 有时候,一些接口里的属性并不是必须的。只是在某些情况下需要存在,带可选属性的接口只需要在可选属性的后面加一个
?
即可。
interface Person {
name : string;
age : number;
phone? : number
}
3. 只读属性
- 一些对象的属性只能在对象刚刚创建的时候修改其值,可在属性名前加上
readonly
来指定只读属性。
interface Point {
readonly x : number;
readonly y : number;
}
let p : Point = {
x : 5, y : 5};
p.x = 190; // error,只能读不能赋值
- ts中具有
ReadonlyArray<T>
类型,它与Array<T>
类似,只是把能够修改数组的方法去掉,因此可以确保数组创建后不被修改:
let a: number[] = [1,2,3,4];
let b: ReadonlyArray<number> = a;
b[0] = 2; // error
b.push(4); // error
h.length = 20; //error
a = b; //error
//正确做法
a = b as number[];
- 若作为变量,使用const
- 若作为属性,使用readonly
4. 额外的属性检查
-
在前面的例子中,我们传入了接口里没有的属性,结果是这个额外的属性被忽略了;但在有可选属性的接口中,这样做将会失败,提示额外的属性不在期望中。要绕开这个检查,使用类型断言即可。
interface SquareConfig { color?: string; width?: number; } function createSquare(config: SquareConfig): { color: string; area: number } { // ... } let mySquare = createSquare({ colour: "red", width: 100 } as SquareConfig);
-
但最佳的方式是在接口里添加一个字符串索引签名。
interface SquareConfig { color?: string; width?: number; [propName: string] : any; }
5. 函数类型
-
接口除了能够描述带有属性的普通对象外,还可以描述函数类型;定义函数时,需要定义调用签名,是只有参数列表和返回值类型的函数定义。
interface searchFunc { (source : string, subString: string) : boolean; } let mysearch : searchFunc; mysearch = function (source : string,subString : string) { let result = source.search(subString); return result > -1; }
-
对于函数类型,函数的参数名不需要和接口里的一致。函数的参数会逐个进行检查,要求对应位置上的参数类型是兼容的。如果不指定类型,typescript会推断出参数类型。函数的返回值类型若与接口的不同,类型检查器会警告我们。
mysearch = function(src : string,sub : string) : boolean { let result = src.search(sub); return result > -1; } // or mysearch = function(src , sub){ let result = src.search(sub); return result > -1; }
6. 可索引的类型
-
可索引类型具有一个索引签名,描述了对象索引的类型,还有相应的索引返回值类型。
interface StringArray{ [index : number] : string; } let myarr : StringArray; myarr = ["yivi","huang"]; let mystr : string = myarr[0];
-
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; }
-
字符串索引签名能够很好的描述
dictionary
模式,并且它们也会确保所有属性与其返回值类型相匹配。 -
因为字符串索引声明了
obj.property
和obj["property"]
两种形式都可以。interface NumberDictionary{ [index: string] : number; length : number; // ok,返回值与索引返回类型一致 name : string; // error,string与索引返回类型不一致 }
-
同时,也可以讲索引类型设置为只读的。
7. 类
-
与Java一样,typescript也能用接口来强制类必须实现的属性或方法。
interface clockInterface{ currentTime : Date; setTime(d : Date); } class Clock implements clockInterface{ currentTime : Date; setTime(d : Date){ this.currentTime = d; } constructor(h : number,m : number){ } }
-
类有两种类型:静态部分的类型和实例类型。
-
当用构造器签名去定义一个接口时并定义一个类去实现时,会报错。
-
因为当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内。
// 以下定义会报错 // Class 'Clock' incorrectly implements interface 'ClockConstructor'. Type 'Clock' provides no match for the signature 'new (hour: number, minute: number): any'. interface ClockConstructor { new (hour: number,minute : number); // 静态类型 } class Clock implements ClockConstructor{ currentTime : Date; constructor(h: number,m:number); }
// 正确示范 interface ClockConstructor { new (hour: number, minute: number): ClockInterface; } interface ClockInterface { tick(); } function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface { return new ctor(hour, minute); // 返回类型为ClockInterface } class DigitalClock implements ClockInterface { constructor(h: number, m: number) { } tick() { console.log("beep beep"); } } let digital = createClock(DigitalClock, 12, 17);
-
因为
createClock
的第一个参数是ClockConstructor
类型,在createClock(DigitalClock, 12, 17)
里,会检查DigitalClock
是否符合构造函数签名。
8. 继承接口
- 和Java一样,typescript也同样支持接口的继承;
- 一个接口可以继承多个接口;
9. 混合类型
-
typescript的好处是具有灵活动态的特点,一个对象可以同时作为函数和对象使用,而且具有额外的属性。
interface Counter{ (start : number) : string; interval : number; reset : void; } function getCounter() : Counter{ let counter = <Counter>function (start : number){ }; counter.interval = 100; counter.reset = function(){ }; return counter; } let c = getCounter(); c(10); c.reset(); c.interval = 20;
10. 接口继承类
-
当接口继承类时,他会继承类的成员但不包括其实现。意味着当你创建一个接口继承的类拥有私有或者保护变量时,这个接口只能被这个类或者其子类实现。
class Control { private state: any; } interface SelectableControl extends Control { select(): void; } class Button extends Control implements SelectableControl { select() { } // ok,因为state为私有成员,所以只有子类才能实现SelectableControl接口 } class TextBox extends Control { select() { } } // 错误:“Image”类型缺少“state”属性。 class Image implements SelectableControl { select() { } }
-
在
Control
类内部,是允许通过SelectableControl
的实例来访问私有成员state
的。 实际上,SelectableControl
接口和拥有select
方法的Control
类是一样的。