接口

接口是一系列形象办法的申明,是一些办法特色的汇合,这些办法都应该是形象的,须要由具体的类去实现,而后第三方就能够通过这组形象办法调用,让具体的类执行具体的办法。
接口解决可用于对类的一部分行为进行形象外,也罕用于对对象的形态进行形容。

接口定义如下:

interface interface_name {}

留神:

  1. 定义接口要首字母大写;
  2. 只须要关注值的形状;
  3. 如果没有非凡申明,定义的变量比接口少了一些属性是不容许的,多一些属性也是不容许的,赋值的时候,变量的形态必须和接口的形态保持一致

    利用场景

    在申明一个对象、函数或者类时,先定义接口,确保其数据结构的一致性;

    实例

    interface IPerson {  name: string;  age: number;}let person:IPerson = {  name: 'xman',  age: 18}

    以上咱们定义了一个接口IPerson,接着定义了一个变量person,它的类型是IPerson。这样,咱们就束缚了 person 的形态必须和接口 IPerson 统一。

变量的形态必须和接口的形态保持一致

interface IPerson {  name: string;  age: number;}let person:IPerson = {  name: 'xman'}// Type '{ name: string; }' is not assignable to type 'IPerson'.// Property 'age' is missing in type '{ name: string; }'.let person2:IPerson = {  name: 'xman',  age: 18,  height: '60kg'}// Type '{ name: string; age: number; height: string; }' is not assignable to type 'IPerson'.// Object literal may only specify known properties, and 'height' does not exist in type 'IPerson'.

接口属性

可选属性

接口里的属性不全都是必须的。在可选属性名字定义的前面加一个?合乎

interface IPerson {  name: string;  age: number;  height?: string;}

益处:

  1. 对可能存在的属性进行预约义;
  2. 捕捉援用了不存在属性时的谬误
    此时仍不容许增加未定义的属性,如果援用了不存在的属性时会呈现错误信息。

    只读属性

    一些对象属性只能在对象刚刚创立的时候批改其值。你能够在属性名前用 readonly 来指定只读属性

    interface IPerson {  readonly name: string;  age: number;}

    批改只读属性值时会Cannot assign to 'xxx' because it is a constant or a read-only property.错误信息
    测试下:

    let person:IPerson = {  name: 'xman';  age: 18;}person.name = 'zxx';  // Cannot assign to 'name' because it is a constant or a read-only property.

    TypeScript 能够通过 ReadonlyArray<T> 设置数组为只读,只是把所有可变办法去掉了,因而能够确保数组创立后再也不能被批改

    let arr: ReadonlyArray<T> = [1, 2, 3];arr[0] = 100;   // Index signature in type 'ReadonlyArray<any>' only permits reading.arr.push(4);  // Property 'push' does not exist on type 'ReadonlyArray<any>'.arr.length = 0;   // Cannot assign to 'length' because it is a constant or a read-only property.

readonly VS const
最简略判断该用readonly还是const的办法是看要把它做为变量应用还是做为一个属性。作为变量应用的话用 const,若作为属性则应用readonly

任意属性

有时咱们心愿接口容许有任意的属性,语法是用[]将属性包裹起来
任意属性有两种定义的形式: 一种属性签名是string类型的, 一种是number类型

任意属性为string类型

interface IPerson {  name: string;  age: number;  [propName: string]: any;}let person:IPerson = {  name: 'xman',  age: 18,  height: '60kg'}

[propName: string]: any是指IPerson类型的对象能够有任意属性签名, string指的是对象的键名是字符串类型的, any则是指定属性值的类型 , 至于propName则相似于函数的形参,是能够取其余名字的。

任意属性为number类型

interface StringArray {   [index: number]: string; }let arr: StringArray = ['xman'];

[index: number]: string是指StringArray的数组能够有任意的数字下标,而且数组成员的类型必须是string。 同时 index也只是相似于函数形参的货色,用其余标识符也是齐全能够的。

能够同时定义两种任意属性吗?

能够, 然而number 类型的签名指定的值类型必须是 string 类型的签名指定的值类型的子集,举个例子:

interface A {  [index: number]: string;  [propName: number]: number;}// Numeric index type 'string' is not assignable to string index type 'number'. 

阐明: string 并不是 number 的子集。
以下例子成立:
因为Function是object的子集:

interface A {  [index: number]: Function;  [propName: number]: object;}

同时定义任意属性和其余类型的属性

注:一旦定义了任意属性, 那么其余属性(确定属性、可选属性、只读属性等)的类型都必须是它的类型的子集

interface IPerson {  name: string;  age?: number;   // Property 'age' of type 'number' is not assignable to string index type 'string'.  [propName: string]: string;}let person:IPerson = {  name: 'xman',  age: 18,  height: '60kg'}// Type '{ name: string; age: number; height: string; }' is not assignable to type 'IPerson'.// Property 'age' is incompatible with index signature.// Type 'number' is not assignable to type 'string'.

以上例子中咱们定义接口IPerson中有一个可选属性age,属性值的类型为number, 任意属性值string,number不是string的子属性,所以报错了。
如何解决?咱们能够应用联结类型:

interface IPerson {  name: string;  age?: number;      [propName: string]: string | number;}let person:IPerson = {  name: 'xman',  age: 18,  height: '60kg'}

对于number类型的任意属性,状况也是一样

type MyArray = {  0: string,   [index: number]: number;}// Property '0' of type 'string' is not assignable to numeric index type 'number'.

然而,number 类型的任意属性签名不会影响其余 string 类型的属性签名:

type A {  [index: number]: number;  length: string;}

如上,尽管指定了 number 类型的任意属性的类型是 number,但 length 属性是 string 类型的签名,所以不受前者的影响。

然而反过来就不一样了,如果接口定义了 string 类型的任意属性签名,它不仅会影响其余 string 类型的签名,也会影响其余 number 类型的签名。

如何绕过多余属性的报错

interface IPerson {  name: string;  age: number;}let person: IPerson = {  name: 'xman',  age: 18,  height: '60kg'}// Type '{ name: string; age: number; height: string; }' is not assignable to type 'IPerson'.// Object literal may only specify known properties, and 'height' does not exist in type 'IPerson'.

类型断言

类型断言是一种通知编译器变量类型的机制。当 TypeScript 确定赋值有效时,咱们能够抉择应用类型断言来笼罩类型。如果咱们应用类型断言,赋值总是无效的,所以咱们须要确保咱们是正确的。否则,咱们的程序可能无奈失常运行。

两种执行类型断言的办法:

  • 应用角括号<>
  • as 关键字
interface IPerson {  name: string;  age: number;}let person: IPerson = {  name: 'xman',  age: 18,  height: '60kg'} as IPerson// 或者let person1: IPerson = < IPerson > {  name: 'xman',  age: 18,  height: '60kg'} 

索引签名

interface IPerson {  name: string;  age: number;  [prop: string]: any;}let person: IPerson = {  name: 'xman',  age: 18,  height: '60kg'}

鸭式辨型法

鸭式辨型来自于James Whitecomb Riley的名言:"像鸭子一样走路并且嘎嘎叫的就叫鸭子。"即具备鸭子特色的认为它就是鸭子, 也就是通过制订规定来断定对象是否实现这个接口。

interface LabeledValue {  label: string;}function printLabel(labelledObj: LabeledValue) {  console.log(labelledObj.label);}let myObj = { size: 10, label: "Size 10 Object" };printLabel(myObj);

类型查看器会查看printLabel的调用。 printLabel有一个参数,并要求这个对象参数有一个名为label类型为string的属性。 须要留神的是,咱们传入的对象参数实际上会蕴含很多属性,然而编译器只会查看那些必须的属性是否存在,并且其类型是否匹配。

interface LabeledValue {  label: string;}function printLabel(labeledObj: LabeledValue) {  console.log(labeledObj.label);} printLabel({ size: 10, label: "Size 10 Object" });   // Argument of type '{ size: number; label: string; }' is not assignable to parameter of type 'LabeledValue'.// Object literal may only specify known properties, and 'size' does not exist in type 'LabeledValue'.

以上代码,在参数里写对象就相当于是间接给labeledObj赋值,这个对象有严格的类型定义,所以不能多参数或少参数。而当你在里面将该对象用另一个变量myObj接管,myObj不会通过额定属性查看,但会依据类型推论为let myObj: { size: number; label: string } = { size: 10, label: "Size 10 Object" };,而后将这个myObj再赋值给labeledObj,此时依据类型的兼容性,两种类型对象,参照鸭式辨型法,因为都具备label属性,所以被认定为两个雷同,故而能够用此法来绕开多余的类型查看。

注: 具体可参考 https://segmentfault.com/a/11...

函数类型

除了形容带有属性的一般对象外,接口也能够形容函数类型。

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

interface SearchFunc {  (source: string, subString: string): boolean;} let mySearch: SearchFunc;mySearch = function(source: string, subString: string): boolean {  return source.search(subString) > -1;}

对于函数类型的类型查看来说,函数的参数名不须要与接口里定义的名字相匹配。你能够扭转函数的参数名,只有保障函数参数的地位不变。函数的参数会被一一进行查看:

interface SearchFunc {  (source: string, subString: string): boolean;} let mySearch: SearchFunc;// source => src, subString => submySearch = function(src: string, sub: string): boolean {  return src.search(sub) > -1;}

如果你不想指定类型,TypeScript 的类型零碎会推断出参数类型,因为函数间接赋值给了 SearchFunc 类型变量。

interface SearchFunc {  (source: string, subString: string): boolean;} let mySearch: SearchFunc;mySearch = function(src, sub) {  let result = src.search(sub);  return result > -1;}

类类型

咱们心愿类的实现必须遵循接口定义,那么能够应用 implements 关键字来确保兼容性。

这种类型的接口在传统面向对象语言中最为常见,比方 java 中接口就是这品种类型的接口。这种接口与抽象类比拟类似,然而接口只能含有形象办法和成员属性,实现类中必须实现接口中所有的形象办法和成员属性。

interface IPerson {  name: string;  age: number;  say(str: string): string;}class Person implements IPerson {  name: string;  age: number;    constructor (name: string, age: number) {    this.name = name;    this.age = age;  }    say (str: string) {    return 'str'  }}

接口形容了类的公共局部,而不是公共和公有两局部。 它不会帮你查看类是否具备某些公有成员.

继承

接口能够通过其余接口来扩大本人。接口可继承多个接口。继承应用关键字 extends。
单继承实例:

interface IAnimal {  name: string;}interface IPerson extends IAnimal{  age: number;}let person = <IPerson> {};person.name = 'xman';person.age = 18;

编译以下代码,失去以下JavaScript代码:

var person = {};person.name = 'xman';person.age = 18;

多继承实例:

interface Shape {  color: string;} interface PenStroke {  penWidth: number;} interface Square extends Shape, PenStroke {  sideLength: number;} let square = {} as Square;square.color = "blue";square.sideLength = 10;square.penWidth = 5.0;

混合类型

对象能够同时做为函数和对象应用,并带有额定的属性

interface Counter {  (start: number): string;  interval: number;  reset(): void;} function getCounter(): Counter {  let counter = function (start: number) { } as Counter;  counter.interval = 123;  counter.reset = function () { };  return counter;} let c = getCounter();c(10);c.reset();c.interval = 5.0;

以上代码,先申明了一个接口,包含number类型的索引签名、reset函数、interval属性
let counter = function (start: number) { } as Counter;通过类型断言,将函数对象转换为 Counter 类型,转换后的对象岂但实现了函数接口的形容,使之成为一个函数,还具备 interval 属性和 reset() 办法。

参考资料

https://www.tslang.cn/docs/ha...
https://juejin.cn/post/685544...
https://segmentfault.com/a/11...