Typescript 接口

Typescript 接口


基本用法

直接使用

这里的{ label: string }就是一个接口,规范labelledObj必须有一个label为字符串的属性
编译器只会检查那些必需的属性是否存在,并且其类型是否匹配
类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以

function printLabell(labelledObj: { label: string }) {
  console.log(labelledObj.label);
}

let myObj = { size: 10, label: "Size 10 Object" };
printLabell(myObj);

使用interface关键字

接口可以单独声明出来,注意接口分隔符写 ,; 都可以,推荐分号,和对象区分

interface LabelledValue {
  label: string;
  name: string;
}

function printLabel1(labelledObj: LabelledValue) {
  console.log(labelledObj.label);
}

let myObj1 = { name: '10', label: "Size 10 Object" };
printLabel1(myObj1);

可选属性

属性后面加个?表示此属性可选

interface xyz {
  x?: number;
  y?: number;
  z?: number;
}

function logXYZ(xyzObj: xyz): { x: number; y: number; z: number } {
  let xyz = { x: 100, z: 100, y: 100 }  // 不关心顺序
  return xyz
}

let xyzObj = { x: 1000, ss: 100, y: 1000, z: 10000 }
logXYZ(xyzObj)

只读属性

属性前面加上readonly表示此属性只读

interface Point {
  readonly x: number;
  readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
// p1.x = 5; // error!

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
// ro[0] = 12;          // error!
// ro.push(5);          // error!
// ro.length = 100;     // error!
// a = ro;              // error!   注意这里,ro是只读的,即使是重新赋值给另外一个变量,也是不允许的
a = <Array<number>>ro   // 但是可以使用断言来重写

额外属性检查

interface Label {
  label: string
}

function printLabel(labelledObj: Label) {
  console.log(labelledObj.label);
}

let myObj2 = { size: 10, label: "Size 10 Object" };
printLabel(myObj2);

上面的接口允许我们传递除了label的额外属性,上面这样写没有问题,但是当我们把myObj2直接写到函数中,ts会校验不通过,这是因为对象字面量会被特殊对待而且会经过额外的属性检查

printLabel({ size: 10, label: "Size 10 Object" });   

解决这个可以使用断言

printLabel(<Label>{ size: 10, label: "Size 10 Object" })

但是注意一下,只有在断言后面直接设置的时候,才能添加额外的属性,下面这样使用会报错

let obj = <Label>{}
obj.label = 'string'
// obj.size = 10  // 这里报错

也可以像上面一样,把对象参数单独保存为一个变量,也可以绕过检查

let myObj3 = { size: 10, label: "Size 10 Object" };
printLabel(myObj3);

最佳方案,添加字符串索引签名,接收任意类型的参数,只要不是label属性,则不关心其类型到底是什么

interface NewLabel {
  label: string;

  // 属性名是string,值是任意值都可以,规定类型为any
  [propName: string]: any
}

function newpPrintLabel(labelledObj: NewLabel) {
  console.log(labelledObj.label);
}

newpPrintLabel({ size: 10, label: "Size 10 Object" })

函数接口

假设有个函数,有两个参数,sourcesubString,都是字符串类型,返回值是布尔类型

  • 通过接口定义,先声明一个函数,指定接口类型,再定义函数
    interface SearchFunc {
      (source: string, subString: string): boolean;
    }
    
    let mySearch: SearchFunc;
    mySearch = function (source: string, subString: string) {
      let result = source.search(subString);
      return result > -1;
    }
    
  • 在定义函数的时候声明接口类型
    interface SearchFunc {
      (source: string, subString: string): boolean;
    }
    
    let mySearch1: SearchFunc = function (source: string, subString: string) {
      let result = source.search(subString);
      return result > -1;
    }
    

ts的类型系统会自动检查,函数的参数会逐个进行检查,要求对应位置的参数类型是相同的,形参不用和接口里的相同,也可以不指定类型

let mySearch2: SearchFunc = function (src, sub) { // 这里入参不用手动添加类型,ts会自动检查
  let result = src.search(sub);
  return result > -1;
}

注意!函数类型的接口里面没有函数名,如果有函数名,就是类接口。并且函数的参数只能少些,不能多写,多写了就会报错

混合类型接口

函数上是可以绑定实例属性的,我们可以队这些属性做接口规范,例如一个函数上可以有interval属性和reset属性,值分别为number和函数,此时我们可以这样定义接口:

interface Counter {
  (start: number): string;
  interval: number;
  reset(): void;
}
// 这里定义一个myCounter,要符合接口的定义,
let myCounter: Counter = <Counter>function (start: number) { }
myCounter.interval = 100
myCounter.reset = function () { }
myCounter.func = function () { } // 报错

类接口

在类中,使用接口需要使用implements关键字继承,并且需要先指定实例属性的类型,和接口中的保持一致
下面定义的接口只能用于类实例上,对于静态属性是无效的。如果想要约束静态属性的类型,往下看静态属性部分。

interface PersonInterface {
  gender: string;
  age: number;
  eat(food: string): string;
}

class Student implements PersonInterface {
  // 和ES6不同的是,TS中属性必须声明,需要指定类型,不写的话ts检查失败
  gender: string
  age: number
  // 声明好属性之后,属性必须赋值一个默认值或者在构造函数中进行初始化
  constructor(gender: string, age: number) {
    this.gender = gender
    this.age = age
  }
  eat(food: string) {
    console.log(food)
    return food
  }
}

let jerry = new Student('male', 18)
console.log(jerry.gender)
console.log(jerry.age)
jerry.eat('米饭')

多继承

implements后面使用,可以继承多个接口

interface interface1 {}
interface interface2 {}
class Cls1 implements interface1, interface2 {}

定义构造函数的接口

当定义类接口中的constructor函数时,有特殊写法,构造函数需要单独写一个接口,定义构造函数的入参类型,以及返回类型是类的接口类型

interface constructorInterface {
  new (name: string): interface3 & interface4;
}
interface interface3 {
  name: string
  log():string
}
interface interface4 {
  show(): string
}

const Cls3:constructorInterface = class implements interface3,interface4 {

  name: string
  constructor(name: string) {
    this.name = name;
  }

  log(){
    return 'loggggg'
  }
  show(){
    return 'showwww'
  }
}

可索引类型

定义索引keynumber类型,索引值为string类型
这个索引签名表示了当用 number去索引StringArray时会得到string类型的返回值。index可以是任意名字,不一定必须是index

interface StringArray {
  [index: number]: string;
}

let myArray: StringArray;
myArray = ["Bob", "Fred"];

let myArray1: StringArray
myArray1 = { 1: 'jack', 2: 'rose' }

let myStr: string = myArray[0];

接口继承

一个接口可以继承自另外一个接口,使用extends关键字

interface AnimalInterface {
  color: string
  age: number
  [propsName: string]: any
}

interface DogInterface extends AnimalInterface {
  jiao(): void
}

class Dog implements DogInterface {
  color: string
  age: number
  name: string
  constructor(name: string, color: string, age: number) {
    this.color = color
    this.age = age
    this.name = name
  }

  jiao() {
    console.log('wangwang~')
  }
}

let daHuang = new Dog('大黄', 'red', 3)
daHuang.jiao()

接口多重继承

interface Shape {
  color: string;
}

interface PenStroke {
  penWidth: number;
}

interface Square extends Shape, PenStroke {
  sideLength: number;
}

先断言再赋值,如果使用了断言,则属性只能设置为接口中定义了的属性

let square = <Square>{};

square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
// square.someOtherProp = 'someOtherProp';    // Error

但如果直接在断言的对象中赋值,则可以赋额外的属性

let square1 = <Square>{
  color: "blue",
  sideLength: 10,
  penWidth: 5.0,
  someOtherProp: 'someOtherProp'
}

如果直接赋值,则一次性必须赋值所有,不能少也不能多

let mySquare: Square = {
  color: "blue",
  sideLength: 10,
  penWidth: 5.0,
}

静态属性部分

这里声明的接口只能作用于实例部分,无法作用于静态部分

interface CarInterface {
  run(): void
  size: number
}

class Car implements CarInterface {
  // CarInterface接口只对实例部分生效,如果实例部分没有定义相应的属性或方法,ts则会报错
  // 下面的代码注释将会报错
  size: number
  constructor(size: number) {
    this.size = size
  }
  run() {
    console.log('跑')
  }

  // 上面接口定义的规则只对实例属性和实例方法有效,对静态属性和静态方法都无效
  // 因此这里的size有没有都不会发生报错
  static size = 1000
  static run() {
    console.log('跑')
  }
}

上面的static size = 1000 这部分其实并没有被接口约束,因为他是静态属性,而上面我们定义的接口只能作用于实例属性,因此这里有没有都不会发生错误,我们需要改造一下,再声明一个类类型的接口,里面包含了构造函数、静态实例、静态方法,再将构造函数的返回值指向实例部分的接口就可以了

// 这里面都是实例部分的属性和方法
interface CarInterface1 {
  piaoyi(): void
  name: string
}

// 声明一个类接口,声明静态方法,其中包含构造函数,静态属性以及静态方法
// 其中构造函数的返回值指向实例部分的类接口
interface CarStatic {
  new(name: string): CarInterface1
  size: number
  run(a: string): void
}

class Car1 implements CarInterface1 {
  name: string
  constructor(name: string) {
    this.name = name
  }

  piaoyi() {
    console.log('漂移~~~')
  }

  static size = 1000
  static run() {
    console.log('跑')
  }
}

let bmw = new Car1('宝马');
console.log(bmw)
console.log(bmw.name)

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 289211569@qq.com