接口类型 -- Typescript基础篇(4)

143 阅读4分钟

js中的对象是十分常见的数据类型,在前面的章节提到过的object类型就能够用来表示对象类型,但是使用object时并没有很好的类型检查和属性提示,在ts中有类型interfce用来定义对象的类型。

interface的功能是对对象进行结构规范和类型检查,官网解释是focuses on the shape that values have

interface SelfIntro {
  name: string;
  age: number;
  location: {
    city: string;
    post: string;
  };
}

const me: SelfIntro = {
  name: "me",
  age: 12,
  location: {
    city: "chengdu",
    post: "000000",
  },
};

在上面例子中,我们定义了一个名为SelfIntrointerface,并规定了该接口有哪些字段以及字段类型,当我们实际使用该结构声明变量时,必须严格按照接口定义赋值。

interface-type-error

一旦使用interface定义的变量,编辑器就能提供很好的属性提示,就像在写静态语言一样。

interface-type-hinter

可选属性

有时候,并不是接口中的所有属性都需要,此时可以用可选属性来表示非必需的属性,使用方式很简单,在属性名的结尾加上?

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

function createSquare(config: SquareConfig): { color: string; area: number } {
  let newSquare = { color: "white", area: 100 };
  if (config.color) {
    newSquare.color = config.color;
  }
  if (config.width) {
    newSquare.area = config.width * config.width;
  }
  return newSquare;
}

let mySquare = createSquare({ color: "black" });

使用可选属性的类型相当于是它自身类型和undefined所组成的联合类型(联合类型对此有更详细的说明)。

interface-optional-properties

虽然可选属性被推导为自身类型和undefined所组成的联合类型,但是它和真正的联合类型还是有所不同。

interface Person {
  name?: string;
  age: number | undefined;
}

// 此时ts会报错:Property 'age' is missing in type '{}' but required in type 'Person'.
// 但是对于可选类型的name则不会提示错误
const p: Person = {};

只读属性

如果我们期望对象的属性在被创建后就不能在更改,此时就可以用只读属性,即在属性名前加上readonly

interface User {
  readonly id: number;
  name: string;
}

const userVal: User = { id: 1, name: "xxx" };

如果后续修改id属性,将会报错。

interface-readonly-error

函数接口

interface除了能描述对象类型,也能描述函数结构(函数章节对此有更详细的说明)。

interface LogFn {
  (message: string): void;
}

// 此时的message会被自动推断为string类型
const fn: LogFn = (message) => {
  console.log(message);
};

如果接口中某个属性是函数,则可以表示为:

interface Data {
  fn(config: any): void;
}

// 或者箭头形式
interface Data {
  fn: (config: any) => void;
}

前端大名鼎鼎的库axios,我们可以直接调用axios,如axios({url:"xxxx"});或者当做对象,使用某个属性,如axios.get("xxxx")。那么它的接口可以这样定义:

interface Axios {
  (config: any): Promise<any>;
  get(url: string): Promise<any>;
  post(url: string): Promise<any>;
}

可索引属性

数组可以使用[]形式访问下标,如a[10]。接口可用可索引属性实现相同的效果。可索引属性形如[key:string | number]: any

interface StringArray {
  [index: number]: string;
}
const myArray: StringArray = ["Bob", "Fred"];
let myStr: string = myArray[0];

前面提到的interface规范对象结构,并且赋值时,要和所声明的结构一一对应,不能多也不能少。但是有时,除了必需属性,我们希望接口还能支持其他任意符合规范的字段,用可索引属性可以做到。

假如,有一个接口用来描述淘宝收货地址,默认有一个主地址,但是其他收货地址并不确定是多少个。

interface TaobaoAddress {
  primaryAddress: string;
  [name: string]: string;
}

const address: TaobaoAddress = { primaryAddress: "xxxx" };
address["secondAddress"] = "xxxxx";
address["thirdAddress"] = "xxxxx";

可索引属性的定义的key类型只能是string或者number

当为string时, 必须兼容已经定义的其他所有属性类型。如上述的TaobaoAddress有个string类型的primaryAddress,所以可索引属性必须兼容string类型,如果可索引属性定义为[name: string]: number则会报错。

当为number时,只需兼容已经定义的其他属性名为number的类型:

interface NewType {
  b: number;
  c?: string;
  1: boolean; // 1 需要被可索引属性兼容
  [key: number]: boolean;
}

const a: NewType = {
  b: 1,
  c: "str",
  1: true,
  2: true,
};

接口继承

在面向对象的语言中,子类可以继承父类的属性。接口也继承另一个接口。通过extends关键字。并且可以多继承。

interface Animal {
  lifetime: number;
}

interface Human extends Animal {
  name: string;
}

const human: Human = { lifetime: 100, name: "me" };

// 可继承多个接口
interface RunFn {
  run(): void;
}

interface Runner extends Human, RunFn {}

const runner: Runner = {
  lifetime: 100,
  name: "me",
  run() {
    console.log("running man");
  },
};