接口:对值所具有的结构进行类型检查,称为“鸭式变型法”或“结构性子类型化”
基本使用
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = {size: 10, label: 'Size 10 Object'};
printLabel(myObj);
LabelledValue
接口定义了labelledObj
的类型结构。printLabel
调用时传入的参数包含label
属性就可以通过LabelledValue
接口的类型检查。同时类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的,就会通过。
可选属性
接口的某些属性是不必存在的,这时就需要设置为可选属性。
在可选属性名字定义的后面加一个?
符号
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
ReadonlyArray<T>
只读数组
数组创建后不能被修改
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[]
字符串索引签名
interface SquareConfig {
color?: string;
width?: string;
[propName: string]: any; // 字符串索引签名,可以绕过额外类型检查。
}
函数类型
接口表示函数类型,需要给接口定义一个调用签名,就像是一个只有参数列表和返回值类型的函数定义。
对于函数类型的类型检查,函数的参数名不需要与接口里定义的名字相匹配
函数的参数会逐个进行检查,对应位置上的参数类型是兼容的。不指定类型,TypeScript
的类型系统会推断出参数类型。函数的返回值类型是通过其返回值推断出来。
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
支持两种索引签名:字符串和数字。可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。
number
去索引时,会先转换成string
,再去索引对象。
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ['Bob', 'Fred'];
let myStr: string = myArray[0];
- 属性类型与索引类型必须匹配
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, 索引签名是只读的
类实现接口
类 实现接口,就要实现接口的属性和方法
接口描述了类的公共部分,不是公共和私有两部分
interface ClockInterface {
currentTime: Date;
setTime(d: Date);
}
class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) {}
}
类静态部分和实例部分的区别
类有:静态部分的类型和实例的类型
- 用构造器签名去定义一个接口并视图定义一个类去实现这个接口时会得到一个错误
interface ClockConstructor {
new (hour: number, minute: number);
}
/**
* 类实现一个接口时,只对其实例部分进行类型检查,`constructor`存在于类的静态部分,不在检查范围内。
*/
class Clock implements ClockConstructor {
currentTime: Date;
constructor(h: number, m: numberF) {}
}
- 直接操作类的静态部分
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface; // 限制的类的实例化 是 `ClockInterface 的子类实例`
}
interface ClockInterface {
tick();
}
function createClock(
ctor: ClockConstructor,
hour: number,
minute: number,
): ClockInterface {
return new ctor(hour, minute);
}
class DigitalClock implements ClockInterface {
constructor(h: number, m: number) {}
tick() {
console.log('beep beep');
}
}
class AnalogClock implements ClockInterface {
constructor(h: number, m: number) {}
tick() {
console.log('tick tick');
}
}
let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);
因为createClock
的第一个参数是ClockConstructor
类型,在 createClock(AnalogClock,7,32)里,会检查AnalogClock
是否符合构造函数签名
继承接口
- 和类一样,接口也可以相互继承。
interface Shape {
color: string;
}
interface Square extends Shape {
sideLength: number;
}
let square = <Square>{};
square.color = 'blue';
square.sideLength = 10;
- 一个接口可以继承多个接口,创建出多个接口的合成接口
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
let square = <Square>{};
square.color = 'blue';
square.sideLength = 10;
square.penWidth = 5.0;
混合类型
例子:一个对象可以同时作为函数和对象使用,并带有额外的属性
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) {
// 访问对象里的属性
console.log('counter', counter.interval);
};
counter.interval = 123;
counter.reset = function () {};
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
接口继承类
接口继承了一个类类型时,会继承类的成员但不包括其实现。就像接口声明了所有类中存在的成员,但并没有提供具体实现一样。
接口同样会继承到类的private
和protected
成员。所以当一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implements)
class Control {
private state: any;
}
interface SelectableControl extends Control {
select(): void;
}
// 子类 Button 继承 `Control` 实现接口`SelectableControl`
class Button extends Control implements SelectableControl {
select() {}
}
// 继承父类 control
class TextBox extends Control {
select() {}
}
// 错误:私有的属性只能由自身类或者其子类实现这个接口
// Image 类缺少 Control类的 `state`属性
class Image implements SelectableControl {
select() {}
}