在很多编程语言中,都有接口这个概念,它表示一系列行为的抽象定义,这些行为具体如何实现交给实现具体的实现类,实现类需要实现这个接口。面向接口编程也是实际开发中一个很重要的规范。
在 TypeScript 中,接口除了用来抽象行为,还用来给对象的字段定义类型,建立开发时的约束:这个类型有哪些字段?字段的类型是什么?因为 JavaScript 是个动态类型的语言,而 TypeScript 的一个核心原则是对结构体进行类型检查。
TypeScript 的类型检查被称为「鸭式辨型法」:像鸭子一样走路并且嘎嘎叫的就叫鸭子。
接口中的属性
TypeScript 的接口用 interface 关键字来定义。接口中可以定义属性,每个属性结尾需要加 ;。声明了接口类型的对象在编码时可以获得智能提示,列出所有定义的属性。看个例子:
interface Person {
name: string;
age: number;
}
let person: Person = {
name: "Owen",
age: 18,
}
console.log("person name = " + person.name + ", age = " + person.age);
编译成 JavaScript 代码后是这样的:
var person = {
name: "Owen",
age: 18
};
console.log("person name = " + person.name + ", age = " + person.age);
所以 JavaScript 中是没有接口这个概念的,它是 TypeScript 新增的一个概念。
可选属性
一般来说,在接口中定义了一些属性,在创建对象时,就必须指定声明的所有的属性,否则编译器会报错。例如:Person 接口中有 name 和 age 两个属性,但创建 person 对象时,只指定了 name 属性,没有指定 age 属性,此时,编译器就会报错:
interface Person {
name: string;
age: number;
}
let person: Person = {
name: "Owen",
}
// Error: 类型 "{ name: string; }" 中缺少属性 "age",但类型 "Person" 中需要该属性。
但有时,某个属性在某些情况下不存在,或者开发时希望列出所有可能会使用到的属性,这个时候我们可以将这个属性设为 可选 的,可选属性在属性名后面加一个 ? 进行标识,可选属性在创建对象时,可以不指定这个属性。
修改一下上面的例子,将 age 属性设为可选属性,在创建对象时,就可以不用指定 age 属性:
interface Person {
name: string;
age?: number;
}
let person: Person = {
name: "Owen",
}
// Ok, age 属性是可选属性,可以不指定。
可选属性的类型是 指定的类型 | undefined 这个联合类型,比如上面例子中 age 属性的类型是 number | undefined,当未指定可选属性时,这个属性获取到的值是 undefined。
interface Person {
name: string;
age: number;
}
let person: Person = {
name: "Owen",
}
console.log("person name = " + person.name + ", age = " + person.age);
// 输出:person name = Owen, age = undefined
可以先通过 if(xxx.optionalProperty) 条件判断可选属性是否被赋过值:
interface Person {
name: string;
age?: number;
}
let person: Person = {
name: "Owen",
}
if (person.age) {
console.log("person has age, age = " + person.age);
}
console.log("person name = " + person.name);
只读属性
有些对象的属性只能在创建对象的时候进行赋值,之后就不能再修改,只能读取。可以在属性名前面加上 readonly 关键字来指定属性是只读的。
interface Point {
readonly x: number;
readonly y: number;
}
let p = Point = { x: 10, y: 10 };
p.x = 5; // 编译错误
接口中的函数
接口中也可以定义函数:
interface Person {
sayHi: () => string;
}
let xiaoming: Person = {
sayHi: (): string => {
return "Hello";
}
};
可索引类型
我们可以把接口作为 Array 或者 Map 来使用,在接口中定义 可索引类型。可索引类型有一个索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。
比如:
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Owen"];
console.log(myArray[0]); // 输出 Bob
这个接口 StringArray 看起来就和一个字符串类型的数组(string[])一样。
索引的类型只能是 number 或者 string。编译器会校验索引值的类型,如果类型不匹配,就会报错,奇怪的是,不会校验索引的类型。再看个 Map 的例子:
interface AgeMap {
[index: string]: number;
}
let ageMap: AgeMap = {};
ageMap["Bob"] = 15; // 正确赋值
console.log(ageMap["Bob"]); // 输出 15
ageMap[2] = "Owen"; // 编译错误:不能将类型“string”分配给类型“number”。
ageMap[2] = 20; // 正确赋值,只会校验值的类型,不会校验索引的类型
console.log(ageMap[2]); // 输出 20
ageMap[true]: 30; // 编译错误:类型“true”不能作为索引类型使用。
可索引类型和接口中的普通字段放在一起的写法如下:
interface NameList {
hello: string;
[index: number]: string;
}
let nameList: NameList = {
hello: "Hello", // 接口中定义的普通字段,必须要有,否则编译错误
0: "Item 0",
1: "Item 1",
"2": "Item 2",
3: 3, // 编译报错,索引值类型不匹配
}
接口继承
接口可以继承另一个接口,接口继承用 extends 关键字表示,子接口可以获得父接口中定义的所有成员。
interface Shape {
color: string;
}
interface Square extends Shape {
sideLength: number;
}
let s = <Square>{}; // 这里使用了类型断言
s.color = "Red";
s.sideLength = 10;
// 上面的写法和下面的写法等价
let s2: Square = {
color: "Red",
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;
参考资料
[TypeScript 中文手册]接口(interface)
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 7 天,点击查看活动详情