learn-typescript

interface 接口

介绍

接口的作用就是为值所具有的结构进行类型命名。

接口初探

interface LabelledValue {
  label: string
}

function printLabel(labelObj: LabelledValue): void {
  console.log(labelObj.label)
}

let myObj = { label: 'size 10 object'}
printLabel(myObj)

接口 LabelledValue 代表了有一个类型为 stringlabel 属性的对象。
需要注意的是,实际上传入的对象可能会包含很多属性,传入多余的属性会报错,因为编译器默认进行额外的属性检查,并非只检查是否包含接口必需的属性以及属性的类型是否正确,详细原因见下方 额外的属性检查

可选属性

可选属性的定义就是在属性名字定义后面加上一个 ? 符号。

interface SquareConfig {
  color?: string
  width?: number
}

只读属性

只读属性只能在刚初始化的时候修改其值,之后再也不能更改。在属性名前面使用 readonly来指定只读属性:

interface Point {
  readonly x: number
  readonly y: number
}

let pt1: Point = {x: 13, y: 12}
pt1.x = 14  // Error

readonlyconst 的使用区别:
readonly 用于修饰属性,而 const 用于修饰变量。

额外的属性检查

interface SquareConfig {
  color?: string
  width?: number
}

function createSquare(config: SquareConfig): {color: string; area: number} {
  // ...
}

let mySquare = createSquare({ colour: 'red', width: 20})  // error: 'colour' not expected in type 'SquareConfig'

注意传入的参数中 colour 不是 color,在 JavaScript 里,这会默默忽略。但是 TypeScript 检测到对象字面量中存在有“目标类型”中不包含的属性时会报错。
绕开这些额外的属性检查,最简单的解决办法是使用 类型断言

let mySquare = createSquare({ colour: 'red', width:20 } as SquareConfig )

最佳的方法是使用 索引签名 ,前提是你能够确定这个对象可能具有某些做为特殊用途使用的额外属性。稍后章节会讲:

interface SquareConfig {
  color?: string
  width?: number
  [propName: string]: any  // 这行代码就是索引签名,描述string类型的键对应any类型的值
}

你可能需要使用这些技巧来绕过额外的属性检查,但是大部分额外属性检查错误是真正的bug。

函数类型

除了描述带有属性的普通对象外,接口也可以描述函数类型。

interface SearchFunc {
  ( source: string, subString: string ): boolean
}

像上面这样定义了函数类型的接口之后,就可以创建一个该函数类型的函数,并且可以不指定函数的参数类型,编译器会根据函数类型来推断出参数类型:

let mySearch: SearchFunc
mySearch = function (src, subStr) {
  let result = src.search(subStr)
  return result > -1
}

如上代码所示,可以不指定 mySearch 函数的入参、返回值的类型,编译器根据 mySearch 的函数类型 SearchFunc 自动推断, 并且 mySearch 函数的入参名不需要与函数类型 SearchFunc 中定义的一致。

上面的例子相当于以下定义变量时指定类型,注意此时使用 => 而不是 :

let mySearch: (src: string, subStr: string) => boolean = function (src, subStr) {
  let result = src.search(subStr)
  return result > -1
}

可索引的类型

接口 可以描述 函数类型 类似, 接口 也可以描述可索引的类型(例如 a[10], obj['name'])。
通过 索引签名 来描述对象索引的类型以及相应索引的返回值类型:

interface Shape {
  name: string
  length: number
  [key: string]: any  // 注意: 索引签名的返回值类型必须包含其他key的返回值类型,这里any不能替换成string,否则与 length: number 不匹配
}

typescript共支持两种索引签名: 字符串索引签名和数字索引签名。
两者可以同时使用,但是 数字索引 的返回值类型必须为 字符串索引 的返回值类型的子类型或相同类型。这是因为 JavaScript会将数字索引转换成字符串索引,也就是说用 100 去索引等同于用 '100' 去索引。

interface Animal {
  name: string
}

interface Dog extends Animal {
  breed: string
}

// 错误,使用字符串索引可能会得到Animal类型实例
interface NotOkay {
  [x: number]: Animal
  [y: string]: Dog
}

索引签名也可以设置为 readonly,防止被重新赋值:

interface ReadonlyInterface {
  readonly [key: string]: any
}

类类型(类实现接口)

C#Java 里的接口基本作用相同,可以使用接口来强制一个类符合某种契约,类使用 implements 实现接口。

interface Person {
  name: string
  age: number
  showInfo(): void
}

class Student implements Person {
  name: string
  age: number
  constructor(name: string, age: number){
    this.name = name
    this.age = age
  }
  showInfo(): void {
    console.log(`student::name: ${this.name}, age: ${this.age}`)
  }
}

接口继承

和类一样,接口也可以相互继承。接口继承使用 extends

interface Shape {
  color: string
}

interface Square extends Shape {
  sideLength: number
}

let squ = {} as Square
squ.color = 'red'
squ.sideLength = 20

一个接口可以继承多个接口,使用逗号隔开。

interface Shape {
  color: string
}

interface PenStroke {
  penWidth: number
}

interface Circle extends Shape, PenStroke {
  radius: number
}

let circle = {} as Circle
circle.color = 'red'
circle.penWidth = 5.0
circle.radius = 20