首页 > 技术文章 > H5前端技术文章 >

TypeScript系列-TypeScript语言特性(五)

更新时间:2018-12-10 | 阅读量(739)

>本文主要对TypeScript中的泛型进行展开介绍。主要包括以下内容 > > ❏ 泛型函数类型 > ❏ 泛型接口(Interface) > ❏ 泛型类(Class) > ❏ 泛型约束 ### 泛型函数的类型 我们已经知道了什么是泛型函数,它跟普通函数还是有些区别的(泛型函数使用类型变量来占位,具体类型值由函数调用传参决定)。以前文章中介绍过TypeScript中的数据类型,以及可选的类型声明。虽然并没有必要(因为可以通过类型推导机制推导出来),但我们确实能够抽取出普通函数的具体类型。下面代码中demo函数的函数类型为:`(name:string,age:number) => string` ```javascript //文件路径 ../08-泛型函数/03-函数的类型.ts //[001] 函数的类型 //(1) 声明demo函数 function demo(name:string,age:number):string { return "姓名:" +name + "年龄:" + age; } //(2) 把demo函数赋值给f let f:(name:string,age:number)=>string = demo; //使用demo函数的调用签名 //let f:{(name:string,age:number):string} = demo; console.log(f("zs",18)); //姓名:zs年龄:18 ``` 接下来,我们花点时间研究,泛型函数的函数类型。其实泛型函数的类型与非泛型函数的类型本质上并没由什么不同,只是有一个类型参数在最前面,下面给出具体的代码示例。 ```javascript function demoT(arg:T):T{ return arg; } //泛型函数demoT的类型为:(arg:T) =>T let f1 : (arg:T) =>T = demoT; //使用带有调用签名的对象字面量来定义泛型函数 let f2 : {(arg:T) :T} = demoT; //可以使用不同的泛型参数名(这里为X) let f3 : (arg:X) =>X = demoT; //不使用类型声明 let f4 = demoT; console.log(f1("abc")); //abc console.log(f2("哈哈")); //哈哈 console.log(f3("嘿嘿")); //嘿嘿 console.log(f4("咕噜")); //咕噜 ``` **提示** 泛型函数的类型声明可以使用不同的泛型参数,只要数量和使用方式一致即可。 ### 泛型接口(Interface) **接口(Interface)**`指的是在面向对象编程语言中,不包含数据和逻辑但使用函数签名定义行为的抽象类型。` TypeScript提供了接口特性,TypeScript的接口可以定义数据和行为,也可以扩展其它接口或者类。 在传统面向对象编程范畴中,一个类可以被扩展为另外一个类,也可以实现一个或多个接口。实现某个接口可以被看做是签署了一份协议,接口相当于协议,当我们签署协议(实现接口)后,就必须遵守它的规则。 `接口本身是抽象类型,其内容(规则)就是属性和方法的签名。` 在前文中我们定义了泛型函数demoT,可以把demoT函数的签名抽取并定义接口GenericFn,下面给出示例代码。 ```javascript //文件路径 ../08-泛型函数/04-泛型接口.ts //(1) 声明泛型函数demoT function demoT(arg:T):T{ return arg; } //(2) 定义GenericFn接口 interface GenericFn{ (arg: T): T; } let fn: GenericFn = demoT; console.log(fn("哈哈")); //哈哈 ``` 有时候,我们可能需要把泛型参数(T)抽取成为整个接口的参数,好处是抽取后我们能够清楚的知道使用的具体泛型类型是什么,且接口中的其它成员也能使用。当我们使用泛型接口的时候,传入一个类型参数来指定泛型类型即可,下面给出调整后的示例代码。 ```javascript //文件路径 ../08-泛型函数/05-泛型接口02.ts //(1) 声明泛型函数demoT function demoT(arg:T):T{ return arg; } //(2) 定义泛型接口 interface GenericFn{ (arg: T): T; } let f1: GenericFn = demoT; console.log(f1(123)); //123 //报错:Argument of type '"字符串"' is not assignable to parameter of type 'number'. //console.log(f1("字符串")); //错误的演示 let f2: GenericFn = demoT; console.log(f2("字符串")); //字符串 ``` ### 泛型类(Class) 泛型特性可以应用在Class身上,具体的使用方式和接口差不多。 ```javascript //文件路径 ../08-泛型函数/06-泛型类.ts //泛型类(Class) class Person{ //[1] 属性部分 name:T; color:T; //[2] 方法部分 add:(a:T,b:T)=>T; } //获取实例对象p1 var p1 = new Person(); p1.name = "张三"; //报错: TS2322: Type '123' is not assignable to type 'string'. //p1.name = 123; 错误的演示 p1.color = "Red"; p1.add = function(a,b){ return a + b; } console.log(p1); //{name:"张三",color:"Red",...} console.log(p1.add("ABC","-DEF")); //ABC-DEF //获取实例对象p2 var p2 = new Person(); p2.name = 0; p2.color = 1; p2.add = function(a,b){ return a + b; } console.log(p2.add(100,200)); //300 ``` 上面的代码提供了泛型类使用的简单示例,在`定义泛型类的时候,只需要直接把泛型类型放在类名(这里为Person)后面即可`,通过new调用类实例化的时候,以<类型>的方式传递,在Class中应用泛型可以帮助我们确认类中的很多属性都在使用相同的类型,且能够优化代码结构。 ### 泛型约束 有时候,我们可能需要对泛型进行约束。下面的代码中我们声明了泛型函数fn,并在fn的函数体中执行`console.log("打印length值 = " + arg.length);`意在打印参数的长度。这份代码在编译的时候会报错,因为无法确定函数调用时传入的参数一定拥有length属性。 ```javascript //文件路径 ../08-泛型函数/02-泛型函数使用注意点.ts //说明 该泛型函数使用类型变量T来表示接收参数和返回值的类型 function fn(arg:T):T{ console.log("打印length值 = " + arg.length); return arg; } //报错:error TS2339: Property 'length' does not exist on type 'T'. console.log(fn([1,2,3])); ``` 其实相比于操作any所有类型的数据而言,在这里我们需要对参数类型进行限制,要求传入的参数能够拥有length属性,这种场景可以使用泛型约束。 理想中泛型函数fn的工作情况是:“只要传入的参数类型拥有指定的属性length,那么代码就应该正常执行。 为此,需要列出对于T的约束要求。下面,我们先定义一个接口来描述特定的约束条件。然后使用这个接口和extends关键字来实现泛型约束,代码如下: ```javascript //文件路径 ../08-泛型函数/07-泛型约束.ts //[001] 定义用于描述约束条件的接口 interface hasLengthP { length: number; } //[002] 声明fn函数(应用了泛型约束) function fn(arg:T):T { console.log("打印length值 = " + arg.length); return arg } //[003] 调用测试 console.log(fn([1,2,3])); //打印length值 = 3 [1,2,3]; console.log(fn({name:"zs",length:1})); //打印length值 = 1 对象内容 //说明:字符串会被转换为对象类型(基本包装类型) console.log(fn("测试")); //打印length值 = 2 测试 //报错:error TS2345: Argument of type '123' is not assignable to parameter of type 'hasLengthP'. console.log(fn(123)); //错误的演示 ``` 上面代码中的fn`泛型函数被定义了约束,因此不再是适用于任意类型的参数。`我们需要传入符合约束类型的值,传入的实参必须拥有length属性才能运行。 **泛型约束中使用多重类型** **提示** 当声明泛型约束的时候,我们只能够关联一种类型。但有时候,我们确实需要在泛型约束中使用多重类型,接下来我们研究下它的可能性和实现方式。 假设现在有一个泛型类型需要被约束,它只允许使用实现Interface_One和Interface_Two两个接口的类型。 ```javascript //文件路径 ../08-泛型函数/08-泛型约束中使用多重类型01.ts //定义接口:Interface_One和Interface_Two interface Interface_One{ func_One(); } interface Interface_Two{ func_Two(); } //泛型类(泛型约束为Interface_One,Interface_Two) class classTest { propertyDemo:T; propertyDemoFunc(){ this.propertyDemo.func_One(); this.propertyDemo.func_Two(); } } ``` 我们可能会像这样来定义泛型约束,然而上面的代码在编译的时候会抛出错误,也就是说`我们不能在定义泛型约束的时候指定多个类型`(上面的代码中我们指定了Interface_One和Interface_Two两个类型),如果确实需要设计多重类型约束的泛型,可以通过把多重类型的接口转换为一个超接口来处理,下面给出示例代码。 ```javascript //文件路径 ../08-泛型函数/09-泛型约束中使用多重类型02.ts //定义接口:Interface_One和Interface_Two interface Interface_One{ func_One(); } interface Interface_Two{ func_Two(); } //Interface_One和Interface_Two成为了超接口,它们是Interface_T的父接口 interface Interface_T extends Interface_One,Interface_Two{}; //泛型类 class classTest { propertyDemo:T; propertyDemoFunc(){ this.propertyDemo.func_One(); this.propertyDemo.func_Two(); } } let obj = { func_One:function(){ console.log("func_One"); }, func_Two:function(){ console.log("func_Two"); } } //获取实例化对象classTestA let classTestA = new classTest(); classTestA.propertyDemo = obj; classTestA.propertyDemoFunc(); //func_One func_Two //下面是错误的演示 let classTestB = new classTest(); //报错:error TS2322: Type '{ func_Two: () => void; }' is not assignable to type 'Interface_T'. classTestA.propertyDemo = { func_Two:function(){ console.log("func_Two_XXXX"); } }; ``` 备注:该文章所有的示例代码均可以[点击在Github托管仓库获取](https://github.com/flowerField/TypeScript-Demo)
叩丁狼学员采访 叩丁狼学员采访
叩丁狼头条 叩丁狼头条
叩丁狼在线课程 叩丁狼在线课程