核心内容摘要
xrk站长统计MBA智库官网入口:洞悉数据洪流,赋能商业智慧,解锁增长密码
前言关于概念本文不会过多叙述。
先来看个例子体会一下泛型解决的问题吧。
我们定义一个print函数这个函数的功能是把传入的参数打印出来最后再返回这个参数传入参数的类型是string函数返回类型为string。
functionprint(arg:string):string{console.log(arg)returnarg}假如现在需求变了我还需要打印number类型请问怎么办可使用联合类型来改造functionprint(arg:string|number):string|number{console.log(arg)returnarg}现在需求又变了我还需要打印string数组、number数组甚至任何类型怎么办直接anyfunctionprint(arg:any):any{console.log(arg)returnarg}需要注意的是写any类型不好毕竟在TS中尽量不要写any。
而且这也不是我们想要的结果只能说传入的值是any类型输出的值也是any类型传入和返回并不是统一的。
这么写甚至还会出现bug。
constres:stringprint(
定义string类型来接收print函数的返回值返回的是个number类型TS并不会报错提示我们。
这个时候泛型就出现了它可以轻松解决输入输出要一致的问题。
另外泛型不是为了解决这一个问题设计出来的泛型还解决了很多其他问题这里是通过这个例子来引出泛型。
基本使用泛型的语法是里写类型参数一般可以用T来表示。
处理函数参数我们使用泛型来解决前面的问题如下代码所示functionprintT(arg:T):T{console.log(arg)returnarg}这样我们就做到了输入和输出的类型统一且可以输入输出任何类型。
如果类型不统一就会报错泛型中的T就像一个占位符、或者说一个变量在使用的时候可以把定义的类型像参数一样传入它可以原封不动地输出。
泛型的写法对前端工程师来说是有些古怪比如 T但记住就好只要一看到就知道这是泛型。
我们在使用的时候可以有两种方式指定类型定义要使用的类型TS 类型推断自动推导出类型printstring(hello)// 定义 T 为 stringprint(hello)// TS 类型推断自动推导类型为 string我们知道type和interface都可以定义函数类型也用泛型来写一下type这么写typePrintT(arg:T)TconstprintFn:Printfunctionprint(arg){console.log(arg)returnarg}interface这么写interfaceIprintT{(arg:T):T}functionprintT(arg:T){console.log(arg)returnarg}constmyPrint:Iprintnumberprint
默认参数如果要给泛型加默认参数可以这么写interfaceIprintTnumber{(arg:T):T}functionprintT(arg:T){console.log(arg)returnarg}constmyPrint:Iprintprint这样默认就是number类型了怎么样是不是感觉T就如同函数参数一样呢
处理多个函数参数现在有这么一个函数传入一个只有两项的元组交换元组的第 0 项和第 1 项返回这个元组。
functionswap(tuple){return[tuple[1],tuple[0]]}这么写我们就丧失了类型用泛型来改造一下。
我们用 T 代表第 0 项的类型用 U 代表第 1 项的类型。
functionswapT,U(tuple:[T,U]):[U,T]{return[tuple[1],tuple[0]]}这样就可以实现了元组第 0 项和第 1 项类型的控制。
传入的参数里第 0 项为 string 类型第 1 项为 number 类型。
在交换函数的返回值里第 0 项为 number 类型第 1 项为 string 类型。
第 0 项上全是 number 的方法。
第 1 项上全是 string 的方法。
函数副作用操作泛型不仅可以很方便地约束函数的参数类型还可以用在函数执行副作用操作的时候。
比如我们有一个通用的异步请求方法想根据不同的 url 请求返回不同类型的数据。
functionrequest(url:string){returnfetch(url).then(resres.json())}调一个获取用户信息的接口request(user/info).then(res{console.log(res)})这时候的返回结果 res 就是一个 any 类型非常讨厌。
我们希望调用 API 都清晰的知道返回类型是什么数据结构就可以这么做interfaceUserInfo{name:stringage:number}functionrequestT(url:string):PromiseT{returnfetch(url).then(resres.json())}requestUserInfo(user/info).then(res{console.log(res)})这样就能很舒服地拿到接口返回的数据类型开发效率大大提高约束泛型假设现在有这么一个函数打印传入参数的长度我们这么写functionprintLengthT(arg:T):T{console.log(arg.length)returnarg}因为不确定 T 是否有 length 属性会报错那么现在我想约束这个泛型一定要有 length 属性怎么办可以和 interface 结合来约束类型。
interfaceILength{length:number}functionprintLengthTextendsILength(arg:T):T{console.log(arg.length)returnarg}这其中的关键就是T extends ILength让这个泛型继承接口 ILength这样就能约束泛型。
我们定义的变量一定要有 length 属性比如下面的 str、arr 和 obj才可以通过 TS 编译。
conststrprintLength(lin)constarrprintLength([1,2,3])constobjprintLength({length:10})这个例子也再次印证了 interface 的 duck typing。
只要你有 length 属性都符合约束那就不管你是 strarr 还是obj都没问题。
当然我们定义一个不包含 length 属性的变量比如数字就会报错泛型的一些应用使用泛型可以在定义函数、接口或类的时候不预先指定具体类型而是在使用的时候再指定类型。
泛型约束类定义一个栈有入栈和出栈两个方法如果想入栈和出栈的元素类型统一就可以这么写classStackT{privatedata:T[][]push(item:T){returnthis.data.push(item)}pop():T|undefined{returnthis.data.pop()}}在定义实例的时候写类型比如入栈和出栈都要是 number 类型就这么写consts1newStacknumber()这样入栈一个字符串就会报错这是非常灵活的如果需求变了入栈和出栈都要是 string 类型在定义实例的时候改一下就好了consts1newStackstring()这样入栈一个数字就会报错特别注意的是泛型无法约束类的静态成员。
给 pop 方法定义 static 关键字就报错了
泛型约束接口使用泛型也可以对 interface 进行改造让 interface 更灵活。
interfaceIKeyValueT,U{key:Tvalue:U}constk1:IKeyValuenumber,string{key:18,value:lin}constk2:IKeyValuestring,number{key:lin,value:18}
泛型定义数组定义一个数组我们之前是这么写的constarr:number[][1,2,3]现在这么写也可以constarr:Arraynumber[1,2,3]数组项写错类型报错实战 - 泛型约束后端接口参数类型我们来看一个泛型非常有助于项目开发的用法约束后端接口参数类型。
importaxiosfromaxiosinterfaceAPI{/book/detail:{id:number,},/book/comment:{id:numbercomment:string}...}functionrequestTextendskeyofAPI(url:T,obj:API[T]){returnaxios.post(url,obj)}request(/book/comment,{id:1,comment:非常棒})这样在调用接口的时候就会有提醒比如路径写错了参数类型传错了参数传少了写在后面泛型Generics从字面上理解泛型就是一般的广泛的。
泛型是指在定义函数、接口或类的时候不预先指定具体类型而是在使用的时候再指定类型。
泛型中的 T 就像一个占位符、或者说一个变量在使用的时候可以把定义的类型像参数一样传入它可以原封不动地输出。
泛型在成员之间提供有意义的约束这些成员可以是函数参数、函数返回值、类的实例成员、类的方法等。
用一张图来