# 接口

在 TypeScript 中,我们可以使用接口 interface 来定义对象类型。

# 介绍

接口是一系列抽象方法的声明,是一些方法特征的集合,这些方法都应该是抽象的,需要由具体的类去实现,然后第三方就可以通过这组抽象方法调用,让具体的类执行具体的方法。

接下来,定义一个简单的接口:

interface Person {
  name: string
  age: number
}

let man: Person = {
  name: 'James',
  age: 30
}

我们定义了一个接口 Person 和变量 man,变量的类型是 Person。 这样我们就约束了该变量的值中对象的 keyvalue 要和接口一致。

需要注意的是:

TIP

  1. 接口规范首字母大写;
  2. 被赋值的变量必须和接口的定义保持一致,参数不能多也不能少;
  3. 类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型正确即可。

# 可选属性

接口的所有属性可能都不是必需的。

interface Person {
  name: string
  age?: number
}

let man: Person = {
  name: 'James'
}

# 只读属性

属性名前使用 readonly 关键字制定为只读属性,初始化后不可更改。

interface Person {
  readonly name: string
  age: number
}

let man: Person = {
  name: 'James',
  age: 30
}

man.name = 'Tom' // Error: Cannot assign to 'name' because it is a read-only property.

# 任意属性

用任意的字符串索引,使其可以得到任意的结果。

interface Person {
  name: string
  age: number
  [x: string]: any
}

let man: Person = {
  name: 'James',
  age: 30,
  height: '180cm'
}

除了 nameage 必须一致以外,其他属性可以随意定义数量不限。

WARNING

一旦定义了任意属性,那么其他属性的类型必须是任意属性类型的子集。

interface Person {
  name: string
  age: number
  [x: string]: string
}

let man: Person = {
  name: 'James',
  age: 30,
  height: '180cm'
}

/**
 * Type '{ name: string; age: number; height: string; }' is not assignable to type 'Person'.
 * Property 'age' is incompatible with index signature.
 * Type 'number' is not assignable to type 'string'.
 */

# 数字索引

可以得到任意长度的数组。

interface StringArray {
  [i: number]: string
}
let chars: StringArray = ['a', 'b']

接口能够描述 JavaScript 中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述对象类型函数类型

# 对象类型接口

示例如下:

interface List {
  readonly id: number
  name: string
  age?: number
}

interface Result {
  data: List[]
}

function render (result: Result) {
  console.log(JSON.stringify(result))
}

首先我们定义了一个 List 对象接口,它的内部有 idnameage 属性。接下来我们又定义了一个对象接口,这个对象接口有只一个属性 data,它类型为 List[]。接下来有一个函数,参数类型为 Result

接下来我们定义一个变量 result,将它传入 render 函数。

let result = {
  data: [
    { id: 1, name: 'A', sex: 'male' },
    { id: 2, name: 'B' }
  ]
}

render(result)

这里需要注意 data 数组内的第一个对象里,增加了一个 sex 属性,但是在上面的接口定义中没有 sex 属性。这时把对象赋给 result 变量,传入函数,不会被编译器检查到。

再看下面的例子:

render({
  data: [
    { id: 1, name: 'A', sex: 'male' },
    { id: 2, name: 'B' }
  ]
})
// Error: Object literal may only specify known properties, and 'sex' does not exist in type 'List'.

我们将对象字面当做参数传给了 render 函数时,编译器会对对象内的属性进行检查。

我们可以通过类型断言规避这个问题

render({
  data: [
    { id: 1, name: 'A', sex: 'male'},
    { id: 2, name: 'B' }
  ]
} as Result)

除了使用 as 关键字,还可以用 <> 符号:

render(<Result>{
  data: [
    { id: 1, name: 'A', sex: 'male'},
    { id: 2, name: 'B' }
  ]
})

# 函数类型接口

为了使用接口表示函数类型,我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。

在数据类型中我们提到过,可以用一个变量声明一个函数类型。

let add: (x: number, y: number) => number

此外,我们还可以用接口来定义它。

interface Add {
  (x: number, y: number): number
}

let add: Add = (a, b) => a + b

除此之外,还有一种更简洁的方式就是使用类型别名

类型别名使用 type 关键字

type Add = (x: number, y: number) => number

let add: Add = (a, b) => a + b

TIP

  • interface 定义函数(Add)和用 type 定义函数(Add)有区别?

typeinterface 多数情况下有相同的功能,就是定义类型。 但有一些小区别:
type:不是创建新的类型,只是为一个给定的类型起一个名字。type还可以进行联合、交叉等操作,引用起来更简洁。
interface:创建新的类型,接口之间还可以继承、声明合并。建议优先使用 interface

Last Updated: 10/29/2019, 3:51:46 PM