{%hackmd BJrTq20hE %} ###### tags: `TypeScript` # TypeScript Generics(泛型) ## 什麼是泛型 泛型在函式裡可以讓函式在使用的時候再決定參數、回傳的資料類型。 ### 函式參數的泛型 ```javascript= function hello<T>(data:T){ console.log(data) } hello<string>("Jack")//Jack ``` ### 函式return的泛型 ```javascript= function hello<T>(data:T):T{ return data } console.log(hello("Jack"))//Jack ``` 泛型的適用範圍就只有在該函式上,所以不同的函式相同的泛型名稱是可以的。 可以看到下方的範例程式碼泛型都是T,不過在實際使用時不會互相影響,因為他們的作用範圍只在函式上。 ```javascript= function hello<T>(data:T){ console.log(data) } function pluse<T>(par1:T, par2:T){ console.log(par1 , par2) } hello<string>("Jack")//Jack pluse<number>(5 , 6)//5 6 ``` ### 如果把上述的par1 + par2則會報錯 ```javascript= function pluse<T>(par1:T, par2:T){ console.log(par1 + par2) } hello<string>("Jack")//Jack pluse<number>(5 , 6) ``` ![](https://i.imgur.com/25Z1sBb.png) 猜測原因是泛型會避免所傳入的型別參數無法做運算而報錯,所以先報錯。 ### 泛型也可以用上type ```javascript= type Obj99 = { name:string, age:number} function hello<T>(data:T):T{ console.log(data) return data } const data:Obj99 = { name:"Jack", age:26} hello<Obj99>(data)// { name:"Jack", age:26} ``` ## function 與 泛型 下方的範例程式碼在說明interface與泛型在function中的使用 ```javascript= // interface的泛型決定isWork的資料類別 interface UserInterface<T> { name: string age: number isWork:T } // function test的泛型決定了所傳入參數的類別之外,也決定了 // return的資料型別為interface UserInterface,又因為 // interface有泛型所以isWork的資料類別也會跟著決定 function test<U>(data:U):UserInterface<U>{ const userData:UserInterface<U> = { name: "Jack", age: 54, isWork:data } return userData } console.log(test<boolean>(true));//{name: 'Jack', age: 54, isWork: true} ``` ## class 與 泛型 下方的範例程式碼在說明interface與泛型在class中的使用 class 的泛型在創建實體的時後決定泛型的類型,如果interface也有泛型,也會在同時決定。 ```javascript= interface CarInterface<T> { carName: string carNumber: number carModel: T } class Car<U> implements CarInterface<U>{ carName: string carNumber: number carModel: U constructor(name: string, number: number, model: U) { this.carName = name this.carNumber = number this.carModel = model } } const myCar = new Car<string>("the new type car", 12345, "fast type") console.log(`carName:${myCar.carName},carNumber${myCar.carNumber},carModel ${myCar.carModel}`) //carName:the new type car,carNumber12345,carModel fast type ``` ## TypeScript Generics - 進階應用 - 什麼是extends條件判斷? extends除了可以用在interface 與 class中,也可以用在泛型的型態判斷 interface與class的extends範例 ```javascript= interface UerInterface{ name:string age:number } interface JackInterface extends UerInterface{ } const JaceData:JackInterface = { name:"Jack", age:7 } class User{ name:string = "Jack" } class Jack extends User{ } const jack = new Jack() console.log(jack.name)//Jack ``` ### 判斷條件的基礎寫法 下方的程式碼是type T的類型判斷,string能不能兼容string可以的話type T的類型就是string不是的話就是number ```javascript= type T = string extends string ? string : number ``` ### interface 與 interface間的比較 interface UserB可兼容interface UserA所以type U是string類別 ```javascript= interface UserA{ name:string } interface UserB{ name:string age:number } type U = UserB extends UserA ? string : number ``` ### union 下方的type U T都是number因為union類不會包含union內的各個資料類別,可以看到下圖type U與type T都是數字類別,因為union沒有包含number或是'jack'。 ```javascript= type U = string | number extends number ? string : number type T = 'Jack' | 'jack' extends 'jack' ? string : number ``` ![](https://i.imgur.com/n0UB68D.png) ![](https://i.imgur.com/CPnHf7S.png) ### nuion與泛型 會一個一個傳入比較 ```javascript= type DemoU<U> = U extends number ? string : number type DemoUU = DemoU<string | number> type DemoT<T> = T extends 'jack' ? string : number type DemoTT = DemoT<'Jack'|'jack'> ``` ![](https://i.imgur.com/3QZVCDs.png) ![](https://i.imgur.com/7Tf9oNv.png) ### never never被視為所有資料類型的子集,結果都會是true ```javascript= type DemoU = never extends number ? string : number ``` ![](https://i.imgur.com/LUDx54z.png) ### never與泛型 但是如果never在泛型中的話會被看作是空的union也是會一個一的帶入,但是帶入的是空值,所以Result也會被當作不會有任何的類型return ```javascript= type DemoT<T> = T extends number ? string : number type Result = DemoT<never> ``` ![](https://i.imgur.com/AnLtEXF.png) 在泛型中never被視為空,所以類別的判斷不會回傳任何東西 ### 如何解決nuion與泛型一個一個傳入比較的問題 使用[]表示要一起把union傳入 ```javascript= type DemoU<U> = [U] extends [string|number] ? string : number type DemoUU = DemoU<string | number> ``` ![](https://i.imgur.com/6SgJk0T.png) 要比較的泛型(extends後)也是要放在[] ```javascript= type DemoU<U> = [U] extends string|number ? string : number type DemoUU = DemoU<string | number> ``` ![](https://i.imgur.com/4uCIWUu.png) ## function中要傳入Array類型的參數要怎麼辦? 在一般的function與泛型使用範例如下 但是這個時候會報錯,因為不知道T是不是真的為Array所以報錯 ```javascript= function doSomethingGood<T>(par:T){ console.log(par.length); } ``` ![](https://i.imgur.com/gNWIg4H.png) 把泛型extend為Arrayy,表示先跟TS說泛型T是Array ```javascript= function doSomethingGood<T extends Array<T>>(par:T){ console.log(par.length); } ``` ![](https://i.imgur.com/lBIQbjC.png) ## infer 可以想成是類型的儲存地,當滿足extends的包容時,才會對infer的內別進行判斷 ### infer 與 Array 以下的程式碼表示泛型T需要為Array,如果是Array則會再另外判斷Array內的資料類別並儲存在P上,如果不是Array則為never。 type R1 的類型為string 因為Array內的資料為string type R2 的類型為never ```javascript= type DemoT<T> = T extends Array<infer P> ? P: never type R1 = DemoT<['Jack']> type R2 = DemoT<123> ``` ### infer也可以用在定義function參數所傳入的類別 與 function infer也可以用在定義function參數所傳入的類別 下方的程式碼表示type FnPar的泛型T只能是有一個參數的function 而type FR會依據type FnPar的泛型來決定型別 ```javascript= type FnPar<T> = T extends (par: infer P) => any ? P : never type FR = FnPar<(data:number)=>void> ``` ![](https://i.imgur.com/H3onFX5.png) 把傳入的參數多加了123則type FR的類別會變為never ```javascript= type FnPar<T> = T extends (par: infer P) => any ? P : never type FR = FnPar<(data:number,123)=>void> ``` ![](https://i.imgur.com/gDFywbk.png) #### infer 在 function 中與interface一起使用 下方的程式碼表示type FnPar的泛型T只能是有一個參數的function 而type FR會依據type FnPar的泛型來決定型別,這個型別也可以是interface ```javascript= type FnPar<T> = T extends (par: infer P) => any ? P : never interface UserInfoInterface {name : string} type FR2 = FnPar<(data:UserInfoInterface)=>void> ``` ![](https://i.imgur.com/YUH9wU1.png) #### 如果泛型不是函式會怎麼樣? T extends不包含函示所以直接判斷T為never ```javascript= type FnPar<T> = T extends (par: infer P) => any ? P : never interface UserInfoInterface {name : string} type FR3 = FnPar<[]> ``` ![](https://i.imgur.com/xYdUUL0.png) ## keyof keyof的功能 1.把interface的key一一的提出來,可以想成是轉換為union(不是sting,number那一種的) 2.在function的泛型告知TS,key的來源 ### 轉換為union ```javascript= interface UserData { name:string age:number job:string location:string } type UserType = keyof UserData // 這裡可以把UserType看為是 'name' | 'age' | 'job'| 'location' 這種union let dataName:UserType = 'name' let dataAge:UserType = 'age' let dataJob:UserType = 'job' let dataLocation:UserType = 'location' // 這個會報錯,因為沒有出現在interface中 let dat:UserType = 'jack' ``` ![](https://i.imgur.com/w2KdCC5.png) ### 在function的泛型告知TS,key的來源 以下的程式碼是很常見的傳入一個物件並且回傳出資料的function,不過以下的寫法TS會認為你該回傳的是Array而報錯 ```javascript= function getData<T,K>(obj:T, key:K):T[K] { return obj[key] } ``` ![](https://i.imgur.com/8zAkQlj.png) 修改為如下 ```javascript= function getData<T,K extends keyof T>(obj:T, key:K):T[K] { return obj[key] } ``` 簡單來說就是跟TS說K是T的key