[3] 接口interface

55 阅读5分钟

1 是什么

(1)定义对象类型
(2)变量名一致,个数和类型一致

2 任意属性

如果 SquareConfig带有上面定义的类型的color和width属性,并且还会带有任意数量的其它属性,那么我们可以这样定义它:索引签名

interface SquareConfig {
    color?: string;
    width?: number;
    [propName: string]: any; // 任意属性
}

注意:使用 [propName: string] : string,定义了任意属性取 string 类型的值。
但是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集-即必须是string类型,否则报错。因此我们一般[propName: string]: any; 来写任意类型。

interface student {
    readonly id: string;  // 只读属性
    name: string;
    age?: number; // 可选属性 // 报错
    gender: string;
    [propName: string]: string // 任意类型为string,则其他属性都应该为string类型。
}
let Jhon = {
    id: '20101102',
    name: 'Jhon',
    age: 18,
    gender: 'male',
    class: 'grade3'
  }
[propName: string]: string// 任意属性的值允许是 string,但是可选属性 age 的值却是 number,number 不是 string 的子属性,所以会报错了

3 类实现接口

明确的强制一个类去符合某种契约。 类去实现接口,必须定义接口里的变量和实现接口里的方法。关键字implements

interface ClockInterface {
   currentTime: Date
   setTime(d: Date)
}

// 类去实现一个接口
class Clock implements ClockInterface{
   currentTime: Date;
   constructor(h: number, m: number) {
   }
   setTime(d: Date) {
       this.currentTime = d;
   }

4 接口继承接口

和类一样,接口也可以相互继承。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。关键字extends

5 接口继承类 --不常用

接口同样会继承到类的 private 和 protected 成员。 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。

image.png

关于继承extends和实现implements

关键字作用
继承extends继承某些属性和方法
实现implements规范自己行为

6 接口(interface)与类型别名(type)区别

参考:mp.weixin.qq.com/s/FzINyDsGc…

相同点

  1. 接⼝和类型别名都可以⽤来描述对象或者函数。
  2. 都可以扩展。

接口的扩展就是继承,通过 extends 来实现。
类型别名的扩展就是交叉类型,通过 & 来实现。

区别

  1. 类型别名更通用
    接口只能声明对象,不能声明基本类型
type A = number
type B = A | string // 接口做不到
  1. 扩展表现不同
    扩展接口时,TS将检查扩展的接口是否可以赋值给被扩展的接口。举例如下:
interface A { 
    good: string,
    bad: string
}

interface B extends A{ 
    good: string,
    bad: number
}

image.png

但是交集类型扩展时,TS将尽其所能把扩展和被扩展的类型组合在一起,而不会抛出编译时错误。 我们将上述代码中的接口改写成类型别名,把 extends 换成交集运算符 &

type A { 
    good: string,
    bad: string
}

type B = A & { 
    good: string,
    bad: number // 此时B的bad类型为string&number类型,也就是never
}

let a: B = {
    good: 'd',
    bad: 'd' // 报错
}

image.png 3. 多次定义时表现不同
接口可以定义多次,多次的声明会合并。但是类型别名如果定义多次,会报错。

interface Point {
    x: number
}
interface Point {
    y: number
}
const point: Point = {x:1} // Property 'y' is missing in type '{ x: number; }' but required in type 'Point'.
const point: Point = {x:1, y:1} // 正确
type Point = {
    x: number // Duplicate identifier 'A'.
}

type Point = {
    y: number // Duplicate identifier 'A'.
}

如何选择interface和type

如果接口和类型别名都能满足的情况下,到底应该用哪个是我们关心的问题。感觉哪个都可以,但是强烈建议大家只要能用接口实现的就优先使用接口,接口满足不了的再用类型别名。

大多数时候,对于声明一个对象,类型别名和接口表现的很相似。

interface Foo { prop: string }
type Bar = { prop: string };

然而,当你需要通过组合两个或者两个以上的类型实现其他类型时,可以选择使用接口来扩展类型,也可以通过交叉类型(使用 & 创造出来的类型)来完成,这就是二者开始有区别的时候了。

  • 接口会创建一个单一扁平对象类型来检测属性冲突,当有属性冲突时会提示,而交叉类型只是递归的进行属性合并,在某种情况下可能产生 never 类型
interface Point1 {
    x: number
}

interface Point extends Point1 {
    x: string // Interface 'Point' incorrectly extends interface 'Point1'.
              // Types of property 'x' are incompatible.
              // Type 'string' is not assignable to type 'number'.
}
type Point1 = {
    x: number
}

type Point2 = {
    x: string
}

type Point = Point1 & Point2 // 这时的Point是一个'number & string'类型,也就是never

从上述代码可以看出,接口继承同名属性不满足定义会报错,而相交类型就是简单的合并,最后产生了 number & string 类型,可以解释译文中的第一点不同,其实也就是我们在不同点模块中介绍的扩展时表现不同

  • 接口通常表现的更好,而交叉类型做为其他交叉类型的一部分时,直观上表现不出来,还是会认为是不同基本类型的组合
  • 接口之间的继承关系会缓存,而交叉类型会被看成组合起来的一个整体
  • 在检查一个目标交叉类型时,在检查到目标类型之前会先检查每一个组分

结论

有的同学可能会问,如果我不需要组合只是单纯的定义类型的时候,是不是就可以随便用了。但是为了代码的可扩展性,建议还是优先使用接口。现在不需要,谁能知道后续需不需要呢?所以,让我们大胆的使用接口吧~