TypeScript(十)泛型進階
目錄
前言
本文收錄于TypeScript知識總結系列文章,歡迎指正!
上篇文章我們領略了泛型的靈活及強大;了解了泛型的基本使用以及常見用法。本文將針對泛型的其他用法做一些進階拓展,其中有許多知識點可以放在前面的文章介紹,但是與泛型放在一起可能更好理解,那么話不多說,直接開始
泛型約束
我們可以通過泛型約束來對泛型參數類型進行限制,確保它符合特定的類型要求,泛型約束的寫法是在關鍵字extends后追加類型來實現,約束的類型可以是任意類型下面是一個簡單的例子
type Animal<T extends hobbyList> = {
name: string
hobby: T
}
type hobbyList = {
length: number
push: (...args: any[]) => number
}
const animal: Animal<hobbyList> = {
name: "阿黃",
hobby: ['ball', 'flying-disc']
}
animal.hobby.push("run")
console.log(animal.hobby); // [ 'ball', 'flying-disc', 'run' ]
可以看到,上面的函數中我們限制了animal傳入的泛型T有且僅包含length屬性及push方法,此時我們對animal中的hobby修改類型或者調用其他方法時就會報錯
animal.hobby.concat(["run"]) // 類型“hobbyList”上不存在屬性“concat”
animal.hobby = {} // 類型“{}”缺少類型“hobbyList”中的以下屬性: length, push
聯合類型+泛型約束
之前的文章中我們接觸到了聯合類型,在泛型的約束中同樣可以應用該場景中,表示泛型類型參數必須是多個類型中的一種,如
type Animal<T extends string | string[]> = {
name: string
hobby: T
}
const animal: Animal<string> = {
name: "阿黃",
hobby: "ball"
}
const animal2: Animal<string[]> = {
name: "阿黃",
hobby: ['ball', 'flying-disc']
}
交叉類型+泛型約束
除了上面的聯合類型外,我們還可以使用交叉類型來限制泛型同時滿足多個類型特征,比如
type Animal<T extends arrayLength & arrayPush> = {
name: string
hobby: T
}
type arrayLength = {
length: number
}
type arrayPush = {
push: (...args: any[]) => number
}
type MyArray<T> = arrayLength & arrayPush & {
forEach: (cb: (item: T, i: number, arr: MyArray<T>) => void) => void
}
const animal: Animal<MyArray<string>> = {
name: "阿黃",
hobby: ['ball', 'flying-disc']
}
animal.hobby.push("run")
console.log(animal.hobby.length); // 3
上面的代碼中type MyArray同時拓展了arrayPush,arrayLength兩個接口,并在其基礎上新增了forEach方法,此時是可以正確賦值給Animal的泛型的
泛型約束泛型
所謂泛型約束泛型,就是在泛型類型參數中使用其他泛型類型參數來約束它的類型,有點套娃的意思,比如我們對上面的代碼做一些修改:在Animal中增加一個getHobby的屬性,這個屬性類型是U(即arrayLength),而T是被約束于U的,所以T依舊可以取MyArray<string>,簡單的說就是T類型是U類型的子類,子類的屬性是只能多不能少的
type Animal<T extends U, U> = {
name: string
hobby: T
getHobby: U
}
type arrayLength = {
length: number
}
type arrayPush = {
push: (...args: any[]) => number
}
type MyArray<T> = arrayLength & arrayPush & {
forEach: (cb: (item: T, i: number, arr: MyArray<T>) => void) => void
}
const animal: Animal<MyArray<string>, arrayLength> = {
name: "阿黃",
hobby: ['ball', 'flying-disc'],
getHobby: {
length: 2
}
}
animal.hobby.push("run")
console.log(animal.getHobby.length);// 2
console.log(animal.hobby.length); // 3
遞歸類型別名
當我們在寫對象的接口與類型別名時可能會遇到樹形結構或者嵌套對象的情況,如原型鏈,菜單的子項,此時我們需要一種遞歸結構,允許我們在類型中調用自己,如
type MenuItem = {
label: string
key: string
url: string
}
type Menu<T> = {
value: T
children?: Menu<T>[]
}
const menu: Menu<MenuItem> = {
value: {
label: '菜單1',
key: 'menu1',
url: '/menu1'
},
children: [
{
value: {
label: '子菜單1',
key: 'child1',
url: '/child1'
},
children: [
]
}, {
value: {
label: '子菜單2',
key: 'child2',
url: '/child2'
},
children: [
]
},
]
}
上面的代碼中我們使用遞歸類型別名實現了一個簡單的二級菜單
條件類型
在JS中,我們都使用過三元運算符:a ? b : c。在TS中也有這種寫法,我們稱其為條件類型,它可以根據類型參數的屬性或其他類型信息選擇類型的一部分,比如
type ReturnObject<T> = T extends { [key: string]: any } ? T : null
type isNotObj = ReturnObject<false>
type isObj = ReturnObject<{ name: "張三" }>
上面的代碼我們實現了對象類型的約束,如果傳入的類型是對象類型則返回該類型,否則返回null
分發條件類型
TS中的分發條件類型可以將復雜的類型轉換分解成更小的類型,并最終將它們組合在一起
比如我們使用條件類型實現一個簡單的類型檢查器,如果是數字,字符或布爾類型就獲取各自的字符串,否則返回other字符串
type IGetType<T> = T extends string ? 'str'
: T extends number ? 'num'
: T extends boolean ? 'bool'
: 'other'
type INum = IGetType<number>// num
type IBool = IGetType<boolean>// bool
type IStr = IGetType<string>// str
type IOther = IGetType<unknown>// other
類型過濾
顧名思義類型過濾就是在一個集合中過濾出符合條件的類型
基于上面的概念,我們可以實現一個include的類型檢查,下面的函數中我們實現了一個類型過濾,只允許字符、數字、布爾類型通過
type MyInclude<T, U> = U extends T ? U : never;
type whiteList = string | number | boolean // 允許的類型
type getString = MyInclude<whiteList, string> // string
type getArray = MyInclude<whiteList, string[]> // never
類型推導
infer關鍵字
在了解類型推導前,我們要先熟悉一下infer關鍵字,在2.8版本的TS中出現了這樣一個MR
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : T;
上述代碼中的infer R表示什么?我們先看看使用場景
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : T;
type IGetStr = MyReturnType<() => string> // string
type IGetNum = MyReturnType<() => number> // number
type IStr = MyReturnType<string>// string
明白了嗎?上面代碼中的infer將函數的返回值提取成R,當我們傳入一個函數類型時,MyReturnType類型就會返回該函數的返回值,否則就返回原類型。
思考以下類型的實現
type MyArrayItem<T> = T extends (infer Item)[] ? Item : T;
type IStr = MyArrayItem<string>// string
type INumArr = MyArrayItem<number[]>// number
上面代碼中我們實現了提取數組的子項類型的功能
回到類型推導
實際上類型推導就是根據函數參數的類型,推導出函數返回值的類型,我們借助上面的MyReturnType對函數的返回值類型進行推導
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : T;
const concatStr = (str1: string, str2: string) => {
return str1 + str2
}
const addNum = (num1: number, num2: number) => {
return num1 + num2
}
type concatStrReturn = MyReturnType<typeof concatStr> // string
type concatNumReturn = MyReturnType<typeof addNum> // number
映射&索引類型
之前的文章中,我們介紹了keyof和in關鍵字,并且使用映射與索引類型對對象類型進行了復制,并將屬性值設置成了string
type IAnimal = {
name: string
age: number
hobby: string[]
}
type IDog = {
[key in keyof IAnimal]: string
}
索引訪問類型
在JavaScript中,我們使用對象的索引取對象屬性:Object.key或者Object['key'],取對象中的key屬性,而在TypeScript中我們可以通過Object[key]來取對象類型的key屬性
于是,我們可以寫一個獲取對象屬性的類型
interface IAnimal {
name: string
age: number
hobby: string[]
}
type GetItem<T, K extends keyof T> = T[K]
type AnimalName = GetItem<IAnimal, 'name'>// string
type AnimalAge = GetItem<IAnimal, 'age'>// number
映射類型
基于上述代碼,我們可以進一步拓展,使用IAnimal[key]表示IAnimal的每一個屬性,這個key可以理解成是一個對象屬性名的集合(聯合類型),即 name | age | hobby
type IDog = {
[key in keyof IAnimal]: IAnimal[key]
}
上述代碼表示的就是IAnimal的每一項
通過這種寫法,我們可以將IAnimal提取成泛型,寫一個通用的類型別名函數,達到遍歷對象每一個屬性并設置成只讀的目的
type ReadonlyObject<T> = { readonly [key in keyof T]: T[key] };
我們來試用一下
type IAnimalReadonly = ReadonlyObject<IAnimal>
/*
等同于
type IAnimalReadonly = {
readonly name: string;
readonly age: number;
readonly hobby: string[];
}
*/
必選屬性
在TS的映射類型中,有許多可選屬性,我們要如何批量改成必選屬性呢?
這個時候我們可以給屬性名添加 '-?' 符號達到該目的,如
type IAnimal = {
name?: string
age?: number
hobby?: string[]
}
type IDog = {
[key in keyof IAnimal]-?: string
}
此時的IDog的每一項就都是必選屬性了
可變屬性
與只讀屬性readonly對應的是可變屬性,和上述必選屬性類似通過在屬性名前加 -readonly 來實現此效果
type IAnimal = {
readonly name: string
readonly age: number
readonly hobby: string[]
}
type Mutable<T> = {
-readonly [key in keyof T]: string
}
type IAni = Mutable<IAnimal>
結語
以上就是文章所有內容,本文針對泛型的進階使用,主要講述了泛型的約束、遞歸類型別名、條件類型、映射和索引類型。以及它們的詳細用法
感謝你看到最后,如果文章對你有幫助還請支持一下!

浙公網安備 33010602011771號