• 接口一方面可以在面向对象编程中表示行为的抽象,另一方面也可以用来描述对象的形状。
  • 可以为类型命名,或者为我们的代码定义契约。

    一、接口的定义

    1. function printCon({label, value}){
    2. console.log(`${label}: ${value}`)
    3. }
    4. printCon({label: '姓名', value: 'leah'})
    printCon 函数的参数是一个对象,包含两个属性 label和value,然后打印出他们的拼接,我们在调用的时候传入一个对象保证有这两个属性就可以,这看似没什么问题。但如果在调用的时候传入的参数并没有这两个属性程序就会报错,Typescript在编译阶段就会发现错误。我们用接口来完善一下这个函数的定义:
    1. interface Arg{
    2. label: string,
    3. value: string
    4. }
    5. function printCon( {label, value}: Arg){
    6. console.log(`${label}: ${value}`)
    7. }
    8. printCon({label: '姓名', age: 18}) // Argument of type '{ label: string; age: number; }' is not assignable to parameter of type 'Arg'.
    9. // Object literal may only specify known properties, and 'age' does not exist in type 'Arg'.ts(2345)
    可以看到,用接口约束了参数的形状之后,再去调用的话就会检查传入的参数是否符合接口 Arg 的形状, 接口Arg 要求这个对象有两个label 和value的属性。只要传入的对象满足这个必要条件就可以,不符合就会报错提示。

    二、接口的属性

    1.可选属性

    接口里的属性不全都是必需的,有些是在某些条件下存在的,或者根本不存在。这就是可选属性。
    1. interface Arg{
    2. label: string,
    3. value?: string
    4. }
    5. function printCon( {label, value}: Arg){
    6. console.log(`${label}: ${value}`)
    7. }
    8. printCon({label: '姓名', value: 'leah'})
    9. printCon({label: '姓名'})
    可选属性名字后面加一个 ? 符号。
    可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。

    2.只读属性

    (1)readonly

    只读属性表示在对象刚刚创建的时候可以修改其值,之后再也不能修改其值。 ```typescript interface Point { readonly x: number; readonly y: number; }

let p11: Point = { x: 10, y: 10 } p11.x = 20 // Cannot assign to ‘x’ because it is a read-only property.ts(2540)

  1. <a name="Msqv2"></a>
  2. ### (2)readonly 与 const
  3. const 定义的变量也不能改变,他与 readonly 的区别是:readonly 用来定义属性,const 用来定义变量。
  4. ```typescript
  5. const Name: string = 'leah'
  6. Name = 'hahaha' //Cannot assign to 'Name' because it is a constant.ts(2588)
  7. const girl = {
  8. name: 'leah'
  9. }
  10. girl.name = 'hahaha'
  11. interface Engineer {
  12. readonly name: string;
  13. }
  14. const engineer: Engineer = {
  15. name: 'leah'
  16. }
  17. engineer.name = 'hahaha' // Cannot assign to 'name' because it is a read-only property.ts(2540)

readonly 只是静态层面的类型检测,实际上并不能阻止对对象的修改,因为在编译为 JavaScript 代码之后,readonly 修饰符并不会存在:
image.png

3.额外属性的检查

继续拿第一个例子来看

  1. interface Arg{
  2. label: string,
  3. value?: string
  4. }
  5. function printCon( {label, value}: Arg){
  6. console.log(`${label}: ${value}`)
  7. }
  8. printCon({label: '姓名', value: 'leah', age: 18}) // Argument of type '{ label: string; age: number; }' is not assignable to parameter of type 'Arg'.Object literal may only specify known properties, and 'age' does not exist in type 'Arg'.ts(2345)

接口要求这个参数需要传 label 和value,但是我们传入了 age,Typescript就会认为这段代码存在bug,这个时候我们就可以给Arg 定义个额外属性,这样就不会报错了:

  1. interface Arg{
  2. label: string,
  3. value?: string
  4. [key:string]: any
  5. }
  6. function printCon( {label, value}: Arg){
  7. console.log(`${label}: ${value}`)
  8. }
  9. printCon({label: '姓名', value: 'leah', age: 18})

三、接口的使用

1.函数类型

使用接口定义函数类型,需要定义函数的参数类型和返回值类型:

  1. interface SumFunc {
  2. (num1: number, num2: number): number
  3. }
  4. let mySum: SumFunc = function(num1: number, num2: number){ // 函数的参数名不需要与接口里定义的名字相匹配
  5. return num1 + num2
  6. }
  7. mySum(1, 2)
  8. mySum('A', 2) // Argument of type 'string' is not assignable to parameter of type 'number'.ts(2345)

SumFunc 接口定义了函数的参数类型以及返回值类型,在实现函数的时候,函数的参数会逐个进行检查,要求对应位置上的参数类型是兼容的。 如果不想指定类型,TypeScript的类型系统会推断出参数类型,因为函数直接赋值给了 SumFunc类型变量。 函数的返回值类型是通过其返回值推断出来的。 如果让这个函数返回布尔值或字符串,类型检查器会警告我们函数的返回值类型与 SumFunc 接口中的定义不匹配。

2.索引类型

image.png

3.混合类型

在JavaScript中函数也是一个对象,所以一个函数也可以是一个对象,我们可以给这个函数添加一些额外的属性:

  1. interface Counter {
  2. (start: number): string;
  3. interval: number;
  4. reset(): void;
  5. }
  6. function getCounter(): Counter {
  7. let counter = <Counter>function (start: number) { };
  8. counter.interval = 123;
  9. counter.reset = function () { };
  10. return counter;
  11. }
  12. let c = getCounter();
  13. c(10);
  14. c.reset();
  15. c.interval = 5.0;

四、接口继承

1.接口单继承

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

  1. interface Shape {
  2. color: string;
  3. }
  4. interface Square extends Shape {
  5. sideLength: number;
  6. }
  7. let square = <Square>{};
  8. square.color = "blue";
  9. square.sideLength = 10;

2.接口与接口之间的多继承

一个接口可以继承多个接口:

  1. interface Shape {
  2. color: string;
  3. }
  4. interface PenStroke {
  5. penWidth: number;
  6. }
  7. interface Square extends Shape, PenStroke {
  8. sideLength: number;
  9. }
  10. let square = <Square>{};
  11. square.color = "blue";
  12. square.sideLength = 10;
  13. square.penWidth = 5.0;

3.接口继承类

当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。 接口同样会继承到类的private和protected成员。 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。
当你有一个庞大的继承结构时这很有用,但要指出的是你的代码只在子类拥有特定属性时起作用。 这个子类除了继承至基类外与基类没有任何关系。 例:

  1. class Control {
  2. private state: any;
  3. }
  4. interface SelectableControl extends Control {
  5. select(): void;
  6. }
  7. class Button extends Control implements SelectableControl {
  8. select() { }
  9. }
  10. class TextBox extends Control {
  11. select() { }
  12. }
  13. // 错误:“Image”类型缺少“state”属性。
  14. class Image implements SelectableControl { // Class 'Image' incorrectly implements interface 'SelectableControl'.Property 'state' is missing in type 'Image' but required in type 'SelectableControl'.
  15. select() { }
  16. }
  17. class Location {
  18. }

在上面的例子里,SelectableControl包含了Control的所有成员,包括私有成员state。 因为 state是私有成员,所以只能够是Control的子类们才能实现SelectableControl接口。 因为只有 Control的子类才能够拥有一个声明于Control的私有成员state,这对私有成员的兼容性是必需的。
在Control类内部,是允许通过SelectableControl的实例来访问私有成员state的。 实际上, SelectableControl接口和拥有select方法的Control类是一样的。 Button和TextBox类是SelectableControl的子类(因为它们都继承自Control并有select方法),但Image和Location类并不是这样的。

image.png

image.png
image.png
key 可以是string 也可以是数字,内部会自动转成string
image.png
image.png
参数类型 返回值类型

image.png

image.png

image.png

image.png

image.png