入个TypeScript的门(7)——类与接口

108 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第13天,点击查看活动详情

接口

我们之前简单说了接口就是用来定义对象类型的,在对对象赋值的时候, 变量的形状必须和接口的形状保持一致

interface Person {
  name: string;
  age: number;
}

let p1: Person = {
  name: 'Tom',
  age: 25,
  sex: 'male' //报错:不能将类型“{ name: string; age: number; sex: string; }”分配给类型“Person”。
};
------------------
interface Person {
  name: string;
  age: number;
}

let p1: Person = { //报错:类型 "{ name: string; }" 中缺少属性 "age",但类型 "Person" 中需要该属性
  name: 'Tom'
};

可见多一些属性和少一些属性都是不行的。那么如果我们希望有些属性不是必要的咋办呢?这时候就需要使用可选属性了,和函数传参的可选参数是一样的。

interface Person {
  name: string;
  age?: number;
}

let p1: Person = {
  name: 'Tom'
};

那么我们需要额外的属性又咋办呢?我们可以使用[]来定义任意属性。

interface Person {
  name: string;
  age?: number;
  [propName:string]:any
}

let p1: Person = {
  name: 'Tom',
  sex:'male'
};

但是我们需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集

interface Person {
  name: string;
  age?: number;  //报错:类型“number | undefined”的属性“age”不能赋给“string”索引类型“string”。
  [propName:string]:string
}

let p1: Person = {
  name: 'Tom',
  sex:'male'
};

这里我们的name和age就必须是string类型了。

我们在class中使用了readonly属性,代表这个属性只能在初始化的时候进行赋值,之后就不能对其进行修改了,而在接口中也有readonly属性,效果和在class中是一样的。

interface Person {
  readonly name: string;
  age?: number; 
  [propName:string]:any
}

let p1: Person = {
  name: 'Tom',
  sex:'male'
};

p1.name = 'xiaoxu'; //报错:无法分配到 "name" ,因为它是只读属性。

接口也有继承

之前我们说过类型类可以使用&实现继承,而接口也可以使用&来实现继承,那除此之外,接口还可以和class一样,使用extends来完成继承:

interface Person {
  name: string;
  age?: number;
}
interface Man extends Person {
    sex: string
}
let p1 = {
    name:'xiaolei',
    age:20,
    sex:'male'
}

类不仅仅是类

我们知道我们在定义一个对象的时候,可以事先声明好这个对象的类型,一般都是使用类型别名或者接口的方式:

type Person = {
  name:string,
  age:number,
  sayName:()=>void
}

let p:Person = {
  name:'xiaolei',
  age:20,
  sayName() {
    console.log(this.name);
  }
}
------------------
interface Person {
  name:string,
  age:number,
  sayName:()=>void
}

let p:Person = {
  name:'xiaolei',
  age:20,
  sayName() {
    console.log(this.name);
  }
}

但是如果我们的对象是一个类构造出来的,我们也得去事先声明类型吗?答案是不用的,因为类Class就相当于一个接口:

class Person {
  public name:string;
  public age:number;
  constructor(name:string,age:number) {
    this.name = name;
    this.age = age;
  }
  sayName():void {
    console.log(this.name);
  }
}


let p:Person = new Person('xiaolei',20);

甚至不是Person的实例我们也可以用class:

let p2:Person = {};//报错:类型“{}”缺少类型“Person”中的以下属性: name, age, sayName
--------------------
let p2:Person = {
    name:'xiaolei',
    age:20,
    sayName() {
        console.log(this.name)
    }
 }

所以呢,我们在TS中创建一个类的时候,不仅仅是创建了这个class类,还创建了一个名字为类名的类型(实例类型)。也就是说上面的class Personinterface Person在某种意义上来说是一样的。

class与interface的关系

我们现在对于class和interface的单独使用都应该比较熟悉了,也说了classinterface在某种意义上是一样的,那这两者之间是不是可以有什么关联呢?答案是肯定的,我们的class里面有个特殊的类---抽象类,就是我们在父类声明公共的方法和属性,而不同的子类去实现它的方式都不一样,那么在这里接口就可以达到和抽象类作用差不多的效果:

interface Person {
  name: string;
  age: number;
  sayName:()=>void
}
class Man implements Person {} 
//报错:类“Man”错误实现接口“Person”。
//     类型“Man”缺少类型“Person”中的以下属性: name, age, sayName
---------------------------
abstract class Person {
  public name;
  public age;
  constructor(name:string,age:number) {
    this.name = name;
    this.age = age;
  }
  abstract sayName():void;
}
class Man extends Person {} //报错:非抽象类“Man”不会实现继承自“Person”类的抽象成员“sayName”。

但是有时候使用接口比使用抽象类更好,因为我们的每一个类都只能继承一个父类,但是我们实现接口却可以实现多个:

interface Person {
  name: string;
  age: number;
  sayName:()=>void
}
interface Eat {
  foodName:string
}
class Man implements Person,Eat {
  constructor(public name:string,public age:number,public foodName:string){
    this.name = name;
    this.age = age;
    this.foodName = foodName;
  }
  sayName() {
    console.log(this.name);
  }
}

所以有时候接口也是有大用的。

接口继承类

除此之外,我们的接口除了能继承接口外,还可以继承类:

class Person  {
  constructor(public name:string,public age:number){
    this.name = name;
    this.age = age;
  }
} 

interface Man extends Person {
 sex:string
}

let p1:Man = {}  //报错:类型“{}”缺少类型“Man”中的以下属性: sex, name, age

所以我说在某些方面,class和interface在某种意义上来说的一样的(声明类型方面)。至于原因嘛,就是class Person不仅仅定义了class类,还声明了Person类型。它就等同于:

class Person  {
  constructor(public name:string,public age:number){
    this.name = name;
    this.age = age;
  }
} 

interface PersonInterface {
    name:string,
    age:number
}

interface Man extends PersonInterface {
 sex:string
}

let p1:Man = {}  //报错:类型“{}”缺少类型“Man”中的以下属性: sex, name, age

let p2 = new Person('xiaolei',20);

这里的PersonInterface与创建class Person时创建的Person类型的等价的,所以我们的interface Man extendes Person其实就是interface Man extends PersonInterface

但是要注意的是,class创建的类在隐式的创建类型时,不会包含constructor构造函数、静态属性以及静态方法的。