【TypeScript】接口
摘要
- 使用
extends
关键字实现接口继承,implements
关键字让类实现接口。 - 接口只能描述对象的外形。
- 可以用接口描述对象、函数、数组(可索引对象)和类。
- 可以在一个接口里定义多种类型的描述。
- 用接口描述函数参数时会有额外的检测机制,可以用类型断言等方法绕过额外的检测。
类型描述
描述对象
接口可以描述
js
中对象拥有的各种外形
interface LabelValue {
label: string, // 必选属性
value?: number, // 可选属性
readonly Y: number, // 只读属性,只能在对象刚刚创建的时候修改其值
}
只读属性
只读属性只能在对象刚刚创建的时候修改其值
用接口定义函数参数时的额外属性检测
对象字面量会被特殊对待而且会经过 额外属性检查,当将它们赋值给变量或作为参数传递的时候。 如果一个对象字面量存在任何“目标类型”不包含的属性时,你会得到一个错误。
下面的例子里接口 LabelValue
定义了一个必填属性 label
,函数 funA
参数定义为 LabelValue
类型,这时如果给该函数传递一个 LabelValue
类型定义中没有的属性时会报错。
interface LabelValue {
label: string;
}
function funA(config: LabelValue) {}
funA({ label: "", a: 1 });
- 解决方案一:类型断言
funA({ label:'', a: 1, } as LabelValue)
- 解决方案二:添加字符串索引签名
注意 propName
不是固定的,改为任何字符串均可。
LabelValue
可以有任意数量的属性,并且只要它们不是 label
,那么就无所谓它们的类型是什么。
interface LabelValue {
label: string;
[propName: string]: any;
}
- 解决方案三:赋值给其他变量
将原本的参数赋值给变量 params
传入函数时不会受到额外的检测。
let params = {
label: "",
a: 1,
};
funA(params);
描述函数
为了使用接口表示函数类型,我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。
函数的参数名不需要与接口里定义的名字相匹配。函数的参数会逐个进行检查,要求对应位置上的参数类型是兼容的。
interface SearchFunc {
(source: string): boolean;
}
let mySearch: SearchFunc = () => true;
mySearch("");
描述可索引的类型
TypeScript 支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用 number 来索引时,JavaScript 会将它转换成 string 然后再去索引对象。 也就是说用 100(一个 number)去索引等同于使用"100"(一个 string)去索引,因此两者需要保持一致。
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
- 数字索引的返回值必须是字符串索引返回值类型的子类型
class Animal {
name: string;
}
class Dog extends Animal {
breed: string;
}
// 错误:数字索引类型“Animal”不能赋给字符串索引类型“Dog”
interface NotOkay {
[x: number]: Animal;
[x: string]: Dog;
}
// 正确
interface NotOkay2 {
[x: number]: Dog;
[x: string]: Animal;
}
将索引签名设置为只读阻止赋值
interface ReadonlyStringArray {
readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!
myArray['2'] = "Mallory"; // success!
描述类
使用 implements
关键字让类实现接口
interface ClockInterface {
currentTime: Date; // 定义属性
setTime(date: Date): Date; // 定义方法
}
class Clock implements ClockInterface {
currentTime: Date;
setTime(date) {
return date;
}
}
类的静态与实例部分,定义类的 constructor
当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor 存在于类的静态部分,所以不在检查的范围内。
要想定义 constructor
,需要使用一些技巧
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);
}
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 tock");
}
}
let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);
接口继承
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape {
sideLength: number;
}
interface Square2 extends Shape, Square { // 继承多个接口
sideLength2: number;
}
let square = <Square2>{};
接口合并
接口合并从根本上说是把双方的成员放到一个同名的接口里。
接口的非函数的成员应该是唯一的。如果它们不是唯一的,那么它们必须是相同的类型,否则编译器会报错。
interface Box {
height: number;
width: number;
}
interface Box {
scale: number;
}
let box: Box = { height: 5, width: 6, scale: 10 };
对于函数成员,每个同名函数声明都会被当成这个函数的一个重载。 后面的接口具有更高的优先级(其重载会在靠前位置)。
如果签名里有一个参数的类型是单一的字符串字面量(比如,不是字符串字面量的联合类型),那么它将会被提升到重载列表的最顶端(依然遵循后来者居上)。
interface Document {
createElement(tagName: any): Element;
}
interface Document {
createElement(tagName: "div"): HTMLDivElement;
createElement(tagName: "span"): HTMLSpanElement;
}
interface Document {
createElement(tagName: string): HTMLElement;
createElement(tagName: "canvas"): HTMLCanvasElement;
}
合并后的 Document 将会像下面这样:
interface Document {
createElement(tagName: "canvas"): HTMLCanvasElement;
createElement(tagName: "div"): HTMLDivElement;
createElement(tagName: "span"): HTMLSpanElement;
createElement(tagName: string): HTMLElement;
createElement(tagName: any): Element;
}
混合类型
一个对象可以同时做为函数和对象使用,并带有额外的属性。
interface Counter {
(start: number): string; // 函数
interval: number; // 属性
reset(): void; // 方法
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();