【愚公系列】2021年12月 Typescript-接口的使用

举报
愚公搬代码 发表于 2021/12/26 15:18:44 2021/12/26
【摘要】 一、接口概念接口的作用:在面向对象OOP的编程中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到一种限制和规范的作用。接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要。 typescrip中的接口类似于java,同时还增加了更灵活的接口类型,包括...

一、接口概念

接口的作用:在面向对象OOP的编程中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到一种限制和规范的作用。

接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要。 typescrip中的接口类似于java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类等。

OOP : Object Oriented Programming

定义标准。

  • 属性类接口
  • 函数类型接口
  • 可索引接口
  • 类类型接口
  • 接口扩展
  1. 类实现接口
  2. 接口可以继承接口
  3. 类可以继承类, 类也可以实现接口
  4. 接口继承类
  5. 总结
  • 接口与type的区分

二. 属性类接口

1.1 未使用接口的情况:

print的参数是一个对象, 但是必须有label这个属性
ts中自定义方法传入参数,对json进行约束


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

// 跳过这些检查的方式, 它就是将这个对象赋值给一个另一个变量: 
// myObj 不会经过额外属性检查,所以编译器不会报错。
print({name:'张三'});  //错误的写法
print({label:'你好'});  //正确的写法

print({
        label:"Size test",
        name:'sss'
} as { label:string });

注意下面写法会提示报错:

print({
      label:"Size test",
      name:'laney'
});

将对象字面量作为参数传递的时候, 对象字面量会被特殊对待而且会经过"额外属性检查"。
如果一个对象字面量存在任何"目标类型"不包含的属性时,你会得到一个错误。

如何跳过这种检查

官网上给了三种方式:

  • 第一种使用类型断言:
print({
        label:"Size test",
        name:'laney'
} as { label:string });
  • 第二种添加字符串索引签名:
function print(labelObj:{ label:string ,[propName: string]: any;}){
        console.log(labelObj);
 }
print({
        label:"Size test",
        name:'laney'
});
  • 第三种将字面量赋值给另外一个变量:

它就是将这个对象赋值给一个另一个变量:
myObj 不会经过额外属性检查,所以编译器不会报错。

let myObj={ size:10,label:“Size 10 Object” };
print(myObj);

这是一个简单的函数,在调用print时,会检查传入参数的类型,并且只检查那些必需属性是否存在,并且类型是否匹配。下面利用接口重写上面的例子

1.2 使用接口

//接口
interface labelValue{
    label:string;
    size:number;
}
//type 别名
type labelObj = {
    label?:string;
    size:number;
}
 function print(labelObj:labelValue){
    console.log( 'labelObj.label' );
    console.log( labelObj );
 }
 let myObj2={size:10,label:"Size 10 Object",name:'Alice' };
 print(myObj2);

在参数类型检查的时候,会发现参数遵循的是接口labelValue的规范,然后就回去检查是不是符合接口所描述的规范。
接口就相当于是一个约束,让大家都遵循。

//接口:行为和动作的规范,对批量方法进行约束
//就是传入对象的约束 属性接口

1.3 类型断言

类型断言 Type Assertion

定义: 可以用来手动指定一个值的类型
语法

  • 方式一: <类型> 值
function getLength(x:number|string):number{
    if((<string>x).length) {
        return (<string>x).length
    } else {
        return x.toString().length
    }
}

  • 方式二: 值 as 类型
function getLength(x:number|string):number{
    if((x as string).length){
        return (x as string).length
    } else {
        return x.toString().length
    }
}

类型断言并非是类型转换,断言一个联合类型中不存在的类型会报错!


function wrong(x:number|string):boolean{
    return <boolean>x    //  报错!
}

1.3 额外的属性检测

interface labelValue{
    label:string;
    size:number;
}
 function print(labelObj:labelValue){
    console.log( 'labelObj.label' );
    console.log( labelObj );
 }

上面1.2 使用接口 的例子,如果这么这么传值会报错
print({
label:‘hello’,
size:30,
name:‘laney’
});

如何绕过额外属性检查,官方给了3种方式

解决方式:

//  第一种使用类型断言
print({
    label1:'hello',
    size:30
} as labelValue);

// 第二种添加字符串索引签名
/* interface labelValue{
    label?:string;
    size:number;
    [propName: string]: any;    //+
} */

//将字面量赋值给另外一个变量
    var labK = {
      label1:'hello',
      size:30
  };
print(labK);

1.4 可选属性

interface FullName{
    firstName:string;   //注意;结束
    secondName:string;
    // secondName?:string; //可选属性,可传可不传
    hello(str:string):string
}
function printName(name:FullName){
    // 必须传入对象  firstName  secondName
    console.log(name.firstName+'--'+name.secondName);
    console.log(name)
}
// printName('1213');  //错误
/*传入的参数必须包含 firstName  secondName*/
var obj={   
    firstName:'张',
    secondName:'三',
    hello(str:string):string{
        return obj.firstName+obj.secondName;
    }
};
printName(obj)


//如果secondName是可选参数,传参的时候可加以不加
// printName({
//     firstName:'firstName'
// })

通过ajax实例演示 属性类接口

/*
       $.ajax({
             type: "GET",
             url: "test.json",
             data: {username:$("#username").val(), content:$("#content").val()},
             dataType: "json"             
         });
         
*/

interface Config{
    type:string;
    url:string;
    data?:string;
    dataType:string;
}

//原生js封装的ajax 
function ajax(config:Config){
   var xhr=new XMLHttpRequest();
   xhr.open(config.type,config.url,true);
   xhr.send(config.data);
   xhr.onreadystatechange=function(){
        if(xhr.readyState==4 && xhr.status==200){
            console.log('chengong');
            if(config.dataType=='json'){
                console.log(JSON.parse(xhr.responseText));
            }else{
                console.log(xhr.responseText)
            }
        }
   }
}
ajax({
    type:'get',
    data:'name=zhangsan',
    url:'http://localhost:3000/test', //api
    dataType:'json'
})

三、函数类型接口

ts中函数型接口,非常类似于java、c#中使用lambda表达式传入匿名函数。
因为对象中仅包含一个函数,这个对象的全部意义也仅在于那个可被外部调用的函数,故而称之为函数型接口。

加密的函数类型接口

函数类型的接口:对方法传入的参数以及返回值进行约束 批量约束

interface encrypt{
    (key:string,value:string):string;
}

var md5:encrypt=function(key:string,value:string):string{
        //模拟操作
        return key+value;
}
console.log(md5('name','zhangsan'));

var sha1:encrypt=function(key:string,value:string):string{
    //模拟操作
    return key+'----'+value;
}

console.log(sha1('name','lisi'));
//对两个数进行运算得到另一个数  函数型接口
interface  CalcTwo{
    (a:number,b:number):number;
}

/**
 * 计算数组被某种算法运算的结果
 * @param {number[]} arr  数组
 * @param {CalcTwo} calc  用户指定的算法函数
 * @param {number} initVal  传入初始值
 * @returns {number}  得到最终运算结果
**/

function calcArr(arr:number[],calc:CalcTwo,initVal:number):number{
      var result=initVal;
      arr.forEach((value)=>{
          result = calc(result,value);
      });
     return result;
}

使用:


 var arr:number[]=[1,3,5,7,9];
 var  add=function(a:number,b:number):number{
      return a+b;
  };
 
 var  multiply=function(a:number,b:number):number{
     return a*b;
 };
console.log("相加",calcArr(arr, add, 0));//25
console.log("相乘",calcArr(arr, multiply, 1));//945



//或者直接传入一个匿名函数亦可
var s1=calcArr(arr,function(a,b){
    return a*b;
},1);
console.log("s1",s1);//945
 
 var s2=calcArr(arr,function (a,b) {
     return (a+1)*(b+1);
 },1);
console.log("s2",s2);//10170


四、可索引接口

(不常用)

可索引接口:数组、对象的约束 (不常用)

ts定义数组的方式
/*
var arr:number[]=[2342,235325]
var arr1:Array<string>=[‘111’,‘222’]
*/

4.1 可索引接口 对数组的约束


 interface UserArr{
     [index:number]:string
 }

 var arr:UserArr=['aaa','bbb'];
 console.log(arr[0]);
 var arr:UserArr=[123,'bbb'];  /*错误*/
 console.log(arr[0]);

4.2 可索引接口 对对象的约束

 interface UserObj{
   [index:string]:string
 }

 var arr:UserObj={name:'张三'};

五、类类型接口 ,利用implements实现接口

类类型接口用来规范一个类的内容。示例代码如下

用的多,和抽象类有点相似,
类实现接口本质上 即类遵循接口的约束,接口里面写了多少个函数、参数,实现的类里面也要写相同的函数、参数。

interface FullName {  
  username: string;  
}  
//Person这个类需要遵循接口FullName的约束
class Person implements FullName {  
  username: string;  
  constructor(n: string) { 
      this.username = n;
  }  
}  

可以在接口中描述一个方法,并在类里具体实现它的功能,如同下面的setName方法一样:

interface FullName {  
  username: string;  
  setName(name: string): void;  
  getName():void;
}  
class Person implements FullName {  
  username: string;    
  constructor(name: string) {
      this.username = name;
   } 
   setName(str: string) {  
      this.username = str;
   } 
  getName(){
    console.log(this.username)
  } 
} 

var p1 = new Person('Alice');
p1.setName('tony');
p1.getName(); 

下面这个不演示,自行学习


interface Animal{
        name:string;
        eat(str:string):void;
    }

class Dog implements Animal{
    name:string;
    constructor(name:string){
        this.name=name;
    }
    eat(){
        console.log(this.name+'吃粮食')
    }
}

var d=new Dog('小黑');
d.eat();

class Cat implements Animal{
    name:string;
    constructor(name:string){
        this.name=name;
    }
    eat(food:string){
        console.log(this.name+'吃'+food);
    }
}

var c=new Cat('小花');
c.eat('老鼠');
    

六、接口扩展

6.1 类实现接口 - 类类型接口 前面已讲


interface ClockInterface{
  currentTime:Date;
  getTime(d:Date):any;
}
class Clock implements ClockInterface{
  currentTime:Date;
  constructor(){
      this.currentTime = new Date()
  }
  getTime(){
      console.log("123");
      console.log(this.currentTime)
  }
  
}
let clock1=new Clock();
clock1.getTime();

类实现接口本质上也是一样的,即类遵循接口的约束,接口里面写了多少个函数、参数,实现的类里面也要写相同的函数、参数。

接口继承就是说接口可以通过其他接口来扩展自己。

Typescript 允许接口继承多个接口。
继承使用关键字 extends。

6.2 接口可以继承接口

interface Animal {
  eat(): void;
}
// Person 接口继承了 接口Animal
// 单接口继承,只继承一个接口
interface Person extends Animal {
  work(): void;
}
//类实现接口  WebFront类实现接口Person
class WebFront implements Person {
  public name: string;
  constructor(name: string) {
    this.name = name;
  }

  eat() {
    console.log(this.name + "喜欢吃馒头");
  }
  work() {
    console.log(this.name + "写代码");
  }
}
var w = new WebFront("小李");
w.eat();

接口的扩展就是给多添加了一些约束。一个接口可以扩展多个接口,当一个接口扩展另一个接口,也继承了该接口的约束。

6.3. 类可以继承类, 同时类也可以实现接口


interface Animal {
  eat(): void;
}

interface Person extends Animal {
  work(): void;
}

class Programmer {
  public name: string;
  constructor(name: string) {
    this.name = name;
  }

  coding(code: string) {
    console.log(this.name + code);
  }
}

//类继承类并实现接口:  WebFront继承类Programmer 并实现接口Person
class WebFront extends Programmer implements Person {
  constructor(name: string) {
    super(name);
  }
  eat() {
    console.log(this.name + "喜欢吃馒头");
  }
  work() {
    console.log(this.name + "写代码");
  }
}
var w = new WebFront("小李");
w.eat();
w.coding("写ts代码");

6.4. 接口继承类


class Point{
    x:number;
    y:number;
    constructor(x:number,y:number){
      this.x = x;
      this.y = y;

        }
        log(){
          console.log('123456');
        }
    }

 interface Point3d extends Point{
        z:number;
 }
 
var point3d:Point3d={
        x:1,
        y:2,
        z:3,
        log(){
            console.log('7777');
        }
    }
point3d.log();//7777

官方解释:当接口继承了一个类类型时,它会继承类的成员但不包括其实现。
也就是说,接口继承类值继承了它的约束条件,具体的值并不继承。

总结

接口扩展(继承)接口
interfaceA extends interfaceB

类实现接口
classA implements interfaceA

接口继承类
interfaceB extends classB

类扩展(继承)类
classA extends classB

有人肯分不清上面时候用extends,什么时候用implements。

记住一句话,只要涉及到继承就是extends.

interface 与 type 声明类型的区别


namespace InterfaceAndType {
    //  1. Objects / Functions
    // 两者都可以用来描述对象或函数的类型,但是语法不同。

    // Interface
    // interface Point {
    //     x: number;
    //     y: number;
    //   }
      
    //   interface SetPoint {
    //     (x: number, y: number): void;
    //   }


    //   Type alias

    type Point = {
        x: number;
        y: number;
      };
      
    type SetPoint = (x: number, y: number) => void;







    // 2. Other Types 类型别名还可以用于其他类型,如基本类型(原始值)、联合类型、元组。Interface不行
       // primitive 基本类型
        type Name = string;

        // object
        type PartialPointX = { x: number; };
        type PartialPointY = { y: number; };

        // union
        type PartialPoint = PartialPointX | PartialPointY;

        // tuple
        type Data = [number, string];

        // dom
        let div = document.createElement('div');
        type B = typeof div;


        // 3. Extend
        // 两者都可以扩展,但是语法又有所不同。此外,请注意接口和类型别名不是互斥的。接口可以扩展类型别名,反之亦然。

        
        // Interface extends interface

        interface PointX { x: number; }
        interface PointY extends PointX { y: number; }

        // Type alias extends type alias

        type PointA = { x: number; };
        type PointB = PointA & { y: number; };

        // Interface extends type alias

        type SizeA = { x: number; };
        interface SizeB extends SizeA { y: number; }

        // Type alias extends interface

        interface SizeX { x: number; }
        type SizeY = SizeX & { y: number; };



    //    4. class Implements

    // 类可以以相同的方式实现接口或类型别名。但是请注意,类和接口被认为是静态的。因此,它们不能实现/扩展命名联合类型的类型别名。
    interface PointV {
        x: number;
        y: number;
      }
      
      class SomePoint implements PointV {
          x:number;
          y:number;
          constructor(x:number,y:number){
                this.x = x;
                this.y = y;
          }
      }
      
      type PointV2 = {
        x: number;
        y: number;
      };
      
      class SomePoint2 implements PointV2 {
        x:number;
        y:number;
        constructor(x:number,y:number){
              this.x = x;
              this.y = y;
        }
      }
      
      type PointXX = { x: number; } | { y: number; };
      
      // FIXME: can not implement a union type
    //   class SomePoint3 implements PointXX {
    //         x:number;
    //         y:number;
    //         constructor(x:number,y:number){
    //             this.x = x;
    //             this.y = y;
    //         }
    //   }

}

总结

interface 和 type 很像,很多场景,两者都能使用。但也有细微的差别:

类型:对象、函数两者都适用,但是 type 可以用于基础类型、联合类型、元祖。
同名合并:interface 支持,type 不支持。
计算属性:type 支持, interface 不支持。
总的来说,公共的用 interface 实现,不能用 interface 实现的再用 type 实现。主要是一个项目最好保持一致。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。