Typescript —— 接口详解

246 阅读5分钟

接口检查的方式

接口是约束类、对象、函数的契约。

Typescript 里接口的作用是为类型定义的契约,Typescript 采用的是"鸭式辨型法"

//定义接口 duck 表示鸭子
interface Duck{
  sayGaga:()=>{};
}
//明明变量天鹅, 不过赋值的对象中也有 sayGaga 方法,则可以判定为 Duck 鸭子
let swan:Duck = {
  sayGaga(){
    return "嘎嘎";
  }
}

还有一点值得提的是,类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以

宽松型接口类型检查

使用普通接口**{}**的方式,类型检查器在检查时不会要求一定要所有属性一致,只要接口中要求的规则,赋值的对象都包含了即可.

function ts(param: { label: string }) {
  console.log(param.label);
}
//接口中未声明size,赋值了也不会报错
let obj = { 
  size: 10,
  label: "Size 10 Object" 
};
ts(obj);

使用普通接口改写

interface labelObj{
  label: string
}
function ts(param:labelObj) {
  console.log(param.label);
}
let obj = { 
  size: 10,//Error 接口中未声明,赋值了也不会报错
  label: "Size 10 Object" 
};
ts(obj)

配置接口的属性

  1. 可选属性
  2. 只读属性
  3. 额外属性
interface labelObj{
  label?: string;//可选属性 用 ? 来表示
  readonly width: number;  //只读属性 readonly,赋值后就不可更改属性值
  [propName: string]: any; //额外属性
}
/** 
    假设直接赋值的是let a:labelObj = {label:"1",color:2},为了不报错,需要使用以下方式避免传入了未定义的属性报错
    1.额外属性
    2.类型断言 {label:"1",color:2} as labelObj 
    3.将{label:"1",color:2} 赋值给一个变量b后再去赋值,由于赋值的变量不会经过类型检查
*/

**注意事项:**对于包含方法和内部状态的复杂对象字面量来讲,你可能需要使用这些技巧,但是大部额外属性检查错误是真正的bug。

ReadonlyArray

变量用 const , 属性用 readonly,特例:Typescript对数组提供了 ReadonlyArray 这样的泛型.

ReadonlyArray只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改但是可以被赋值成一个新的数组,也不可以赋值给一个普通数组,除非是用类型断言.

let arr:ReadonlyArray<number> = [1];
arr.length = 10;//error
arr[0] = 1;//error
arr.push(20);//error
arr = [2];// success 可以被整个重新赋值 
let clone:number[] = [];
clone = arr; //error 不可以赋值给一个普通数组
clone = (arr as Array<number>);//使用类型断言 success

使用接口的好处

  1. 接口的多种方式都是可以捕获引用了不存在属性时发生的错误.
  2. 可选属性的好处是对可能存在的属性进行预定义.
  3. 只读属性的好处是可以确保属性创建并且赋值后的数据再也不能被修改.

函数类型接口

函数类型接口不要求所赋值的函数参数的名字要一致,只要符合相同位置的类型一致和返回值一致即可.

interface fun{
  (x:number,y:string):string;
}
let fucCode: fun = function(count:number,id:string):string{
  return count + id;
}

也可在赋值的函数中不声明参数类型,由typescript类型检查器自动推断

interface fun{
  (x:number,y:string,z?:number):string;
}
let fucCode: fun = function(count,id){
  return count + id;
}
fucCode(1,"1");

可索引类型接口

描述可以通过索引得到的类型,比如 a[10] a[ 'key' ] 来获取,并且还可以描述返回的类型。

interface array{
  [index:number]: undefined; 
  [index:string]:string;
}
let arr:array = ["1","2"];
//ts支持两种索引:字符串和数字,但是数字索引的返回值类型必须是字符串索引返回值的子类,否则会报错,因为arr[1],javascript会将1转成字符串去索引对象,所以数字索引的返回值类型必须包含于字符串索引返回值的类型中,下面的例子两个例子会报错
class Animal {
    name: string;
}
class Dog extends Animal {
    breed: string;
}
// ERROR:使用数值型的字符串索引,有时会得到完全不同的Animal!
interface NotOkay {
    [x: number]: Animal;
    [x: string]: Dog;
}
interface NumberDictionary {
  [index: string]: number;
  length: number;    // 可以,length是number类型
  name: string       // 错误,`name`的类型与索引类型返回值的类型不匹配
}

类类型接口 implements

可以定义一个类的契约,描述的是公共的部分,而不是公共和私有两部分。并不会去检查公共成员还是私有成员。

interface Animal{
  name:string;
  say():string;
}
class dog implements Animal{
  name:string;
  constructor(){
    this.name = "狗狗";
  }
  say(){
    return "欧欧";
  }
}

不可以在在实现的接口中去检测new 创建的构造函数,因为constructor属于类中的静态部分

//如果实现的接口对contructor函数做了检查,会发生报错
interface Duck{
  new (x:numbery:string):any;
}
class blackDuck implements Duck{
  constructor(x:number,y:string){}
}
检测构造函数需要通过一个另外的函数来检查,原理是 newObj 函数的第一个参数类型是DuckContructor,当你传入一个函数时,Typescript会去检查
该类是否符合。
interface DuckContructor{
  //构造函数返回类型是 接口 Duck
  new (x:numbery:string):Duck; 
}
interface Duck{
  tick():any;
}
//实现duck接口,对trick方法实现。
class blackDuck implements Duck{
  constructor(x:number,y:string){}  
  tick(){}
}
function newObj(Fun:DuckContructor,x:number,y:string):Duck{
  return new Fun(x,y);
}
newObj(blackDuck,1,"hahah");

接口继承接口 extends

接口可以相互继承形成,合成各样的接口。

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

接口继承类

接口继承类时,这个接口只可以给所继承的类的子类去实现,并且继承的只会是类的成员public、private、protected,而不会去继承类的实现。这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。

class Animal{
  name : string;
}
interface Bird extends Animal{
  select(): void;
}
class SiteBrid extends Animal implements Bird{
  select(){}
}
// Error,Image不是 Animal 的子类。
class Image implements Bird{
  select(){}
}

混合类型接口

接口可以定义上面所有的类型,例如一个既视函数又是方法又有属性的时候,就需要详细的去定义类型:例如

interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}
function getCounter(): Counter {
    let counter = <Counter>function (start: number) { };
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}