Typescript-接口

1,317 阅读4分钟

ts 的核心原则之一是对值所具体的结构进行类型检查。接口的作用就是为这些类型命名代码定义规范

接口初探

function printLabel(labelObj: { label: string }) {
  console.log(labelObj.label);
}

printLabel({
  label: "q",
});
  • printLabel 函数接收一个参数
  • 这个参数是一个对象
  • 并要求这个对象中有一个 label 属性,而且类型为字符串

使用接口重写上面的例子

关键字 interface

// 使用接口重写
interface LabelValue {
  label: string;
}

function printLabel(labelObj: LabelValue) {
  console.log(labelObj.label);
}

printLabel1({
  label: "q",
});

那么这么定义接口的好处是什么呢?

那其中之一就是:

比如我们又定义了一个函数,参数与 printLabel 的类型一致。我们不需要为每一个函数都定义重复的约束,直接可以使用同一个接口来约束即可

可选属性

定义可选属性,只需在属性后面加一个 ? 符号

interface LabelValue {
  label: string;
  value?: string;
}

function printLabel({ label, value }: LabelValue) {
  let str = label;
  if (value) {
    str += value;
  }
  console.log(str);
}

printLabel({
  label: "q",
});
printLabel({
  label: "q",
  value: "q",
});

只读属性

属性前使用 readonly 来指定只读属性,然后尝试修改它时会报错。

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

let p: Point = { x: 10, y: 20 };

p.x = 20; // error

对象的属性可以定义只读属性,那么数组同样可以定义只读

ts 具有 ReadonlyArray 类型,它与 Array 类似,只是把所有的可变方法去掉了,因此保证数组创建之后不能被修改

let arr: ReadonlyArray<string> = ["1", "2"];

arr[0] = "1"; // error

arr.push("3"); // error

我们尝试将只读数组赋值给普通的数组:

let arr: ReadonlyArray<number> = [1, 2];
let list: Array<number>;
list = arr; // 类型 "readonly number[]" 为 "readonly",不能分配给可变类型 "number[]"

上面的最后一行代码显示,把只读数组 arr 赋值到一个新数组也不行。

此时如果需要赋值,可以使用类型断言处理。

list = arr as Array<number>;

额外的属性检查

对象字面量会被特殊对待并且会经过额外的属性检查,当将他们赋值给变量或者传递给参数时。如果对象字面量存在接口不包含的属性时,就会出现错误。

说白了其实就是:

如果你没有在接口里声明这个属性,但是传递参数时,你又传了这个属性,我就说你错了!

请看下面的案例

interface LabelValue {
  label: string;
  value?: string;
}

function printLabel({ label, value }: LabelValue) {
  console.log(label + value);
}

printLabel({
  label: "q"
  name: "qzy",
});
  • 在接口里只定义了 labelvalue
  • 但你现在传了一个 name 是什么意思?
  • 我就给你提示错误,不给你通过编译

那么该如何处理呢?

最简单的方法是使用类型断言

printLabel({
  label: "q",
  name: "qzy",
} as LabelValue);

最佳的方案是添加一个字符串索引签名,就是预留一个属性位置

interface LabelValue {
  label: string;
  value?: string;
  [propName: string]: any;
}

printLabel({
  label: "q",
  name: "qzy",
  age: 18,
});

还有一种跳过类型检查的方法是:将这个对象赋值给另外一个变量

这个还是要归于额外的属性检查的定义。

let obj = {
  label: "q",
  name: "qzy",
  age: 18,
};
printLabel(obj);

函数类型

接口不仅可以描述对象,也可以描述函数类型

这个可以这么理解:我可以约束函数的参数,返回值。只要按照规则定义好即可。

比如定义下面的函数类型:

  • 参数 age 为数字
  • 返回值是一个 boolean
interface IsAgent {
  (age: number): boolean;
}

let fun: IsAgent = (age: number): boolean => {
  return age > 18;
};

fun(19);

对于函数类型的类型检查,函数的参数名不需要与接口里定义的相匹配

如果你不想指定类型, ts 也会进行推断,我们可以使用下面的代码进行重写

interface IsAgent {
  (age: number): boolean;
}

let fun: IsAgent = (ag) => {
  return ag > 18;
};

fun(12);

可以少写一些代码。

可索引的类型

描述了对象索引的类型,还有相应的索引返回值类型

可以约束对象或者数组对应的索引的类型值

// 下标为数字 值为字符串
interface StringArray {
  [index: number]: string;
}

// 数组每项必须全部时字符串
let arr: StringArray = ["1", "2"];
// 下标为字符串 值为字符串
interface NumberObj {
  [index: string]: number;
}

// 对象的值必须是数字
let obj: NumberObj = {
  age: 18,
};

类类型

ts 也能够用接口明确的强制一个类去符合某种规则

你可以在接口里去描述一个方法,在类里去实现它

interface myPer {
  name: string;
  love(): void;
}
// 必须包含 name、love
class Person implements myPer {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  love(): void {
    console.log(`${this.name}喜欢吃肉`);
  }
}

接口继承

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

interface Shape {
  color: string;
}

interface Square extends Shape {
  width: number;
}

// 必须存在 color、width 属性
let data: Square = {
  color: "red",
  width: 100,
};

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

interface Shape {
  color: string;
}

interface Square {
  width: number;
}

interface Local extends Shape, Square {
  local: string;
}

let data: Local = {
  color: "red",
  width: 100,
  local: "中国",
};