上一节,我们简单的介绍ts的基础数据类型,那么接下来我们来介绍ts的接口。
TS的接口
接口是一系列抽象方法的声明,是一些方法特征的集合,这些方法都应该是抽象的,需要由具体的类去实现,然后第三方就可以通过这组抽象方法调用,让具体的类执行具体的方法。
JS中是没有接口的概念的,在 TypeScript 中,我们使用接口interface
来定义接口。
在ts中,接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对对象的形状进行描述。
我们先来看一个简单的代码,来认识一下接口。
interface Person {
name: string,
age: number
}
function sayHello(person: Person) {
return `Hello! I am ${person.name}, ${person.age} years old.`;
}
let p1: Person = {
name: "cyl",
age: 21
}
console.log(sayHello(p1));
我们定义了一个简单的接口,用来对Person对象的形状进行描述。我们在调用sayHello函数时,多给一些属性,或者属性数据类型错误都是不行的。
赋值的时候,变量的形状必须和接口的形状保持一致。
可选属性
有时我们希望不要完全匹配一个形状,那么可以用可选属性。
interface Person {
name: string,
age?: number
}
function sayHello(person: Person) {
return `Hello! I am ${person.name}`;
}
let p1: Person = {
name: "cyl"
}
console.log(sayHello(p1));
我们在age的:前面添加一个?表示这是一个可选属性,可以不用给。
只读属性
我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly
定义只读属性
interface Person {
readonly id: string,
name: string,
age?: number
}
let p: Person = {
id: '1',
name: "jake",
age: 16
}
p.id = "2";
我们在id属性前面加上readonly
那么id属性就变成一个只读属性,对其进行修改会报错的。
const
和readonly
的区别,如果你想限定某个变量不允许其发生改变,那么你应该使用const
。如果你想限定一个对象的某个属性不允许发生改变,那么你应该使用readonly
。
任意属性
首先,我们先看一段ts代码
interface Person {
name?: string,
age: number
}
function sayHello(person: Person) {
return `Hello! I am ${person.name}, ${person.age} years old.`;
}
console.log(sayHello({nam: "tom", age: 16}))
不难发现,这段代码在写的时候编译器就会提示报错。
但是有时候,我们难免传递了一些不重要的参数 ,但是不想一个一个的去兼容它们,这个时候就需要任意属性了。
interface Person {
name?: string,
age: number,
[propName: string]: any
}
没有问题了。
函数类型接口
直接看代码吧,对于函数类型接口,我们只需要定义参数类型和返回值类型
interface SearchStr {
(source: string, subString: string): boolean
}
let mySearch: SearchStr = function (sou: string, sub: string) {
let index = sou.indexOf(sub);
return index > -1;
}
索引类型
ts支持俩种类型的索引签名,如下:
interface NumberArray {
[index: number]: string
}
const arr1: NumberArray = ["bob", "alice"];
console.log(arr1[0]);
interface StringArray {
[index: string]: string
}
const arr2: StringArray = {
"name": "bob",
"age": "16"
}
console.log(arr2['name']);
一种以数字为下标,一种以字符串为下标。
当你声明一个索引签名时,所有明确的成员都必须符合索引签名
// ok
interface Foo {
[key: string]: number;
x: number;
y: number;
}
// Error
interface Bar {
[key: string]: number;
x: number;
y: string; // Error: y 属性必须为 number 类型
}
当然你可以在同一个接口使用俩种索引类型
string 类型的索引签名比 number 类型的索引签名更严格。这是故意设计,它允许你有如下类型
interface ArrStr {
[key: string]: string | number; // 必须包括所用成员类型
[index: number]: string; // 字符串索引类型的子级
// example
length: number;
}
当然索引签名也是可以设置为只读的,通过添加readonly
来实现。
上面主要是介绍了接口对于对象的形状进行描述,接下来介绍接口的另一个功能对于属性和行为的抽象。
类类型
我们直接来看代码吧!
interface Person {
name: string,
age: number,
speak()
}
class Student implements Person {
name: string
age: number
constructor(n: string, a: number) {
this.name = n;
this.age = a;
}
speak() {
console.log(`I am ${this.name}, ${this.age} years old.`)
}
}
const s = new Student("tom", 21);
s.speak();
我们定义了一个接口,里面含有name,age俩个属性然后一个speak方法,然后我们又声明了一个Student类使用implements
关键字去实现了这个接口。PS: 对于TS的类,我会在下篇博客学习,不着急。
当一个类使用implements
关键字去实现接口时,那么对于接口里面定义的属性,方法(非可选的)都必须实现,不然就会报错。
简单的说就是,接口只是声明了这些方法和属性,实现类必须要去实现它。
我想你应该了解了一点了,那么我在深入一点。
在TS中,类上面存在俩种不同的部分,一种被称为实例部分属,另一种被称为静态部分。
假如你之前使用过其他面向对象编程语言如c++,c#等,那么你肯定知道static
关键字。如果你不清楚,也没有关系,你熟悉JS即可。
你可以这样来理解,类的实例部分就是当类被实例化的时候才会被初始化的属性,而类的静态部分存在于类本身上面而不是类的实例上。
class Student {
name: string
age: number
static id: string = "12138";
constructor(n: string, a: number) {
this.name = n;
this.age = a;
}
speak() {
console.log(`I am ${this.name} , ${this.age} years old.`)
}
}
console.log(Student.id);
我们看一下上面代码,其中id
就是静态部分,我们不需要实例化Student即new Student(...)
就可以访问。
好了,回归到接口上面来,我们上面定义的接口是作用于实例部分的,name age speak()
都是Student实例部分。那么如何定义静态部分的。
在此之前,我们需要知道构造函数constructor
是属于静态部分的,我们来看如下代码。
interface PersonConstructor { //定义一个构造器接口
new (name: string, age: number)
}
interface PersonAction { //一个包括speak方法的普通接口
speak()
}
//定义一个工厂函数,用于生成一个对象
//ctor类必须实现PersonAction接口
function createPerson(ctor: PersonConstructor, name: string, age: number) : PersonAction {
return new ctor(name, age);
}
//实现接口的 学生类
class Student implements PersonAction {
name: string
age: number
static type: string = "student";
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
speak () {
console.log(`I am a student.`)
}
}
//实现接口的老师类
class Teacher implements PersonAction {
name: string
age: number
static type: string = "teacher";
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
speak () {
console.log(`I am a teacher.`)
}
}
const s1 = createPerson(Student, "小明", 15);
const t1 = createPerson(Teacher, "小王", 25);
s1.speak();
t1.speak();
我们定义了一个构造器接口用来限定传入的类的构造器的形状,并且用它生成一个实例。
接口的继承
接口和类一样,都是可以继承的,我们使用extends
关键字来实现这一点。
interface Color {
color: string
}
interface Size {
size: number
}
interface Rectangle extends Color, Size {
}
const s = {} as Rectangle;
s.color = "red";
s.size = 20;
Rectangle 接口继承了Color, Size接口,那么一个以Rectangle 为形状的对象 是可以有color,size属性的。
混合类型
这个其实非常简单,就是一个接口里面含有多种类型的属性。
interface Person {
name: string,
speak(): void,
eat(food: string): void,
}
这就是一个混合类型的接口。
接口继承类
什么叫接口继承类?简单的说就是一个接口它继承了一个类,这个接口就会拥有这个的类的实例部分的成员,但是不包括其实现。
class Person {
name: string
age: number
static type: string = "student";
constructor () {}
speak() {}
}
interface S extends Person {
}
const s = {} as S;
为了验证是否继承了其实现,看如下代码
class Person {
name: string
age: number
static type: string = "student";
constructor () {}
speak() { console.log(666) }
}
interface S extends Person {
}
const s = {} as S;
s.speak();
然后编译,运行 发现
ts的类