乐趣区

关于typescript:TypeScript基础之接口Interfaces

接口

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

接口定义 如下:

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 => sub
mySearch = 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…

退出移动版