Ts接口

215 阅读7分钟

接口的作用

在面向对象的编程中,接口是一种规范的定义,它定义了行为和 动作的规范,在程序设计里面,接口起到一种限制和规范的作用。 接口定义了某一批类所需要遵守的规范,接口不关心这些类的内 部状态数据,也不关心这些类里方法的实现细节,它只规定这批 类里必须提供某些方法,提供这些方法的类就可以满足实际需要。 typescrip中的接口类似于java,同时还增加了更灵活的接口类 型,包括属性、函数、可索引和类等。

基本使用

对传入对象进行约束,下面是接口的定义:

interface FullRule{
    firstName:string;
    lastName:string;
}

下面是用接口对批量方法进行约束:

function printName(name: FullRule) {
    // 必须传入对象  firstName  lastName
    console.log(name.firstName + '---' + name.lastName);
}
function printInfo(info: FullRule) {
    
    console.log(info.firstName + '+++' + info.lastName);
}

虽然这是两个不同的方法,但是他们遵循FullRule接口规范后,必须包含interface里面设置的,也就是必须需要传入对象包含firstName和 lastName。再看看看他的使用:

let objInterface = {  
    age: 20,
    firstName: "约德尔",
    lastName: "小炮"
}
printName(objInterface)
printInfo(objInterface)

image.png

这里要说明一下为什么不直接把数据放进参数中,而是定义了一个对象,然后把对象当成参数放进。直接放进去的代码如下:

printName({
    age : 20 ,
    firstName : "约德尔",
    lastName : "小炮"
})

代码报错,报错信息:“age”在类型“FullRule”中不存在。如图:

image.png 由此我们可以总结出如果想仅仅把接口定义的内容传到函数中可以直接写进,但是如果我们想在接口定义的数据外再加入其他数据就需要定义一个对象包含进去。

无返回值函数

上面讲的都是有返回值的函数,这里就讲讲无返回值函数怎么写的。和上面比较类似,依旧需要写返回值类型,因此这里写void。如下代码所示:

function fun6():void {
    console.log("void");        
}
fun6()  

image.png

可选参数

es5里面方法的实参和行参可以不一样,但是ts中必须一样,如果不一样就需要配置可选参数,注意:可选参数必须配置到参数的最后面。 这里如果这么写会报错:

function fun7(name:string,age:number):string {
    if(age){
        return `${name}  ---   ${age}`
    }else{
        return `${name}  ---   年龄保密`
    }
}
fun7('张三')//报错

image.png 此时我们可以用可选参数这种写法:

 function fun8(name:string,age?:number):string {
    if(age){
        return `${name}  ---   ${age}`
    }else{
        return `${name}  ---   年龄保密`
    }
}
console.log(fun8('张三'));

可选参数可以让这个age不传也不会报错。
image.png

默认参数

es5里面没法设置默认参数,es6和ts中都可以设置默认参数。

function fun9(name:string,age:number = 20):string {
    if(age){
        return `${name}  ---   ${age}`
    }else{
        return `${name}  ---   年龄保密`
    }
}
console.log(fun9('张三'));    
console.log(fun9('张三',40)); 

image.png

剩余参数(...运算符)

可以把传入的参数一次性加入函数,例如这里实现一个将所有参数进行累加。

 function sumadd(...result:number[]) {
    let sumRes = 0;
    for (let index = 0; index < result.length; index++) {
        sumRes +=  result[index];
    }  
    return sumRes; 
}
console.log(sumadd(5,8,9,5)); 

除了可以直接把内容放进去以外,还可以选择性放入,前面指定后面不指定。

function sumPrint(a:number,b:string,...result:number[]):string {
    let sumRes:string = String(a) + "---";
    for (let index = 0; index < result.length; index++) {
        sumRes +=  String(result[index]) + "---";
    }  
    sumRes = sumRes +  b ;
    return sumRes; 
}
console.log(sumPrint(5,"8",9,5)); 

image.png

ts函数重载

java中方法的重载,重载指的是两个或者两个以上同名函数,但它们的参数不一样,这时会出现函数重载的情况。typescript中的重载:通过为同一个函数提供多个函数类型定义来试下多种功能的目的。ts为了兼容es5 以及es6重载的写法和java中有区别。 咱们先来看看ES5出现同名方法的情况。

function people(name) {
    console.log(name);
}

function people(name, age) {
    console.log(`${name}的年龄${age}`);
}
people('小明')
people('小明', 19)

image.png 很显然这里下面的方法替换了上面的方法。接下来我们看看TS的重载。

function getInfo1(name:string):string; 
function getInfo1(age:number):number; 
function getInfo1(str:any):any{
    if(typeof str ==='string'){
        return "我叫:"+str
    }else{
        return "我的年龄是:"+str
    }
};    
console.log(getInfo1("张三"));
console.log(getInfo1(66));

image.png

除了对参数类型不一样的情况下重载,还可以对参数个数不一样的情况下进行重载。

function getInfo2(name:string):string; 
    function getInfo2(name:string,age:number):string; 
    function getInfo2(name:any,age?:any):any{
        if(age){
            return "我叫:" + name + ",年龄是:" + age
        }else{
            return "我叫:" + name
        }
    }; 
    console.log(getInfo2("李四"));
    console.log(getInfo2("李四",50));

image.png

那我们前面用的可选参数在重载中可以用到吗?

function getInfo3(name:string):string; 
function getInfo3(name:string,sex:string):string; 
function getInfo3(name:string,sex:string,grade:string):string; 
function getInfo3(name:string,sex?:string,grade?:string):string{
    if(grade){
        return "我叫:" + name + ",性别是:" + sex + ",等级是:" + grade
    }else if(sex){
        return "我叫:" + name + ",性别是:" + sex
    }else{
        return "我叫:" + name
    }
}; 
console.log(getInfo3("李四"));
console.log(getInfo3("李四","男"));
console.log(getInfo3("李四","男","A"));

image.png

由图可见,之前说的可选参数在这里依旧可用。

接口的可选属性

刚刚讲的是有接口属性之外的属性怎么放进用了接口的情况,现在讲讲传入的参数没有达到接口要求的情况。大家对这个可选应该不陌生,js里面也有可选属性,这里就直接把它搬过来,多的介绍也不讲了,我们之间看使用案例:

interface PersonSelect {
    name: string;
    age?: number;
    sex: string
}
function knowPerson(per: PersonSelect) {
    console.log(per.name);
    console.log(per.age);
}
knowPerson({
    name: '基督教',
    sex: "男"
})

image.png
上面是一段正常使用可选属性的代码,如果我们不把age定义为可选属性而且不传age这个属性,结果会是什么情况呢?我们把上面代码仅仅把 age?: number; 这行代码改动为 age: number;下面我们看看运行结果:

image.png

很显然出现报错信息说缺少age属性。接下来我们来分析上面代码,这里将age属性定义为可选属性,因此这里age属性就变得可传可不传,传了正常显示,不传也不会出现缺少属性age这种报错。

函数类型检测

接口能够描述JavaScript中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。下述代码encrypt接口定义了函数需要有两个string类型的参数,而且它的返回值也是string类型的。enc引入了encrypt接口,函数功能完成了两个字符串的拼接。最后进行打印。

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

let enc: encrypt = function (a: string, b: string) {
    return a + b;
}
console.log(enc('my name is', 'jetson'));

image.png

可索引接口:数组、对象的约束

数组定义

我们先来回顾一下怎么定义数组的:

let arrNum:number[] = [23,58]
let arrString:Array<string> = ['23','58']

我们回到正题看看具体使用(对数组和对象其实差不多):\

接口对数组进行约束

interface UseArr{
    [index:number]:string
}
let portArr:UseArr = ['you','me']
console.log(portArr[1]);

如果我们在定义UserArr数组的时候不全部是string类型的数据,比如写一个number类型的就可能出现如下报错: image.png\

接口对对象进行约束

interface UseObj{
    [name:string]:string | number ,
}
let portObj:UseObj = {name:'詹桑',age:18,sex:'男'}
console.log(portObj);

image.png

和上面一样我们验证一下如果不按接口约束来写会出现什么问题,这里UseObj我们写成:
let portObj:UseObj = {name:'詹桑',age:18,sex:'男',125}

image.png

类类型接口

对类的约束和抽象类有点相似。这里的eat函数说明一下:接口定义了需要有这个方法,哪怕没有这个参数也是对的。接口描述了类的公共部分,而不是公共和私有两部分。 它不会帮你检查类是否具有某些私有成员。

interface PortClass{
    name:string;
    eat(str:string):void;
    action(str:string):void;
}
class PortClassEx implements PortClass{
    name: string;
    constructor(name:string){
        this.name = name;
    }
    eat(): void {
        console.log(this.name + '吃肉');        
    }
    action(sport: string): void {
        console.log(this.name + '正在' + sport);    
    }
}
let pce = new PortClassEx('小狗')
pce.eat()
pce.action('run')

image.png
我们对着代码来说一下大概意思:这个接口定义了类需要有name属性,eat函数与action函数。接下来就是实例化与运行eat函数与action函数。

接口拓展

接口可以继承接口,用的extends关键字,这里展示的是继承一个接口,事实上可以继承多个接口,例如这么写:interface Square extends Shape, PenStroke { sideLength: number; }

interface InterfaceExStart{
    eat():void;
}
interface InterfaceExEnd extends InterfaceExStart{
    work():void;
}
class InterfaceEx implements InterfaceExEnd{
    name:string;
    constructor(name:string){
        this.name = name
    }
    eat(): void {
        console.log(this.name+'在吃饭');        
    }
    work(): void {
        console.log(this.name+'在工作');        
    }
}
let inf = new InterfaceEx('小李')
inf.eat()
inf.work()

image.png

我们验证一下接口是否继承成功,在InterfaceEx类中注释eat函数。出现如下报错说明接口继承成功。

image.png

类继承的同时使用接口

这里我将继承和接口用在一起,我们看看代码与运行结果。这里WebClass类是父类,TsClass类是子类,子类除了继承父类以外还用InterfaceEx接口来规范。父类中定义了name和sport这两个属性,还定义了code函数,在子类中父类定义的两个属性以及这个函数都继承过来。

class WebClass {
    name : string;
    protected sport : string;
    constructor( name:string,sport:string ){
        this.name = name;
        this.sport = sport;
    }
    code():void{
        console.log(this.name+'在写代码!');
    }
}

class TsClass extends WebClass implements InterfaceEx{
    constructor(name:string , sport:string){
        super(name, sport);
    }
    eat(): void {
        console.log(this.name+'在吃饭');        
    }
    work(): void {
        console.log(this.name+'在'+this.sport);        
    }
}
let tsExample = new TsClass('小李',"看需求文档")
tsExample.eat()
tsExample.work()
tsExample.code()

image.png