在?TypeScript?中使用泛型的方法
前言:
泛型是靜態(tài)類(lèi)型語(yǔ)言的基本特征,允許將類(lèi)型作為參數(shù)傳遞給另一個(gè)類(lèi)型、函數(shù)、或者其他結(jié)構(gòu)。TypeScript 支持泛型作為將類(lèi)型安全引入組件的一種方式。這些組件接受參數(shù)和返回值,其類(lèi)型將是不確定的,直到它在代碼中被使用。下面將通過(guò)一些示例,探索如何在函數(shù)、類(lèi)型、類(lèi)和接口中使用泛型,以及使用泛型創(chuàng)建映射類(lèi)型和條件類(lèi)型。
1. 泛型語(yǔ)法
首先來(lái)看看TypeScript 泛型的語(yǔ)法。泛型的語(yǔ)法為 <T>
,其中T
表示傳入的類(lèi)型。在這種情況下,T
和函數(shù)參數(shù)的工作方式相同,其作為將在創(chuàng)建結(jié)構(gòu)實(shí)例時(shí)聲明的類(lèi)型的占位符。因此,尖括號(hào)內(nèi)指定的泛型類(lèi)型也稱(chēng)為泛型類(lèi)型參數(shù)。泛型的定義可以有多個(gè)泛型類(lèi)型采參數(shù),例如:<T, K, Z>
。
注意:通常使用單個(gè)字母來(lái)命名泛型類(lèi)型。這不是語(yǔ)法規(guī)則,我們也可以像 TypeScript 中的任何其他類(lèi)型一樣命名泛型,但這種約定有助于向閱讀代碼的人傳達(dá)泛型類(lèi)型不需要特定類(lèi)型。
下面通過(guò)一個(gè)函數(shù)的例子來(lái)看看泛型的基本語(yǔ)法。假設(shè)有一個(gè) JavaScript 函數(shù),它接受兩個(gè)參數(shù):一個(gè)對(duì)象和一個(gè)包含key
的數(shù)組。 該函數(shù)將基于原始對(duì)象返回一個(gè)新對(duì)象,其僅包含想要的key
:
function pickObjectKeys(obj, keys) { let result = {} for (const key of keys) { if (key in obj) { result[key] = obj[key] } } return result }
在 pickObjectKeys()
函數(shù)中,遍歷了keys
數(shù)組并使用數(shù)組中指定的key
創(chuàng)建一個(gè)新對(duì)象。下面來(lái)測(cè)試一下這個(gè)函數(shù):
const language = { name: "TypeScript", age: 8, extensions: ['ts', 'tsx'] } const ageAndExtensions = pickObjectKeys(language, ['age', 'extensions'])
這里聲明了一個(gè)language
對(duì)象,然后使用pickObjectKeys()
函數(shù)將 language
對(duì)象中的age
和extensions
屬性組成了一個(gè)新的對(duì)象ageAndExtensions
,
其值如下:
{ age: 8, extensions: ['ts', 'tsx'] }
如果想將這個(gè)函數(shù)遷移到 TypeScript 以使其類(lèi)型安全,則可以使用泛型。
重構(gòu)的代碼如下:
function pickObjectKeys<T, K extends keyof T>(obj: T, keys: K[]) { let result = {} as Pick<T, K> for (const key of keys) { if (key in obj) { result[key] = obj[key] } } return result } const language = { name: "TypeScript", age: 8, extensions: ['ts', 'tsx'] } const ageAndExtensions = pickObjectKeys(language, ['age', 'extensions'])
<T, K extends keyof T>
為函數(shù)聲明了兩個(gè)參數(shù)類(lèi)型,其中K
被分配給了一個(gè)類(lèi)型,該類(lèi)型是T
中的 key
的集合。然后將obj
參數(shù)設(shè)置為T
,表示任何類(lèi)型,并將keys
設(shè)置為數(shù)組,無(wú)論K
是什么類(lèi)型。
當(dāng)傳入的obj
參數(shù)為language
對(duì)象時(shí),T
將age
設(shè)置為number
類(lèi)型,將extensions
設(shè)置為string[]
類(lèi)型,所以變量ageAndExtensions
的類(lèi)型為:
{ age: number; extensions: string[]; }
這樣就會(huì)根據(jù)提供給 pickObjectKeys
的參數(shù)來(lái)判斷返回值的類(lèi)型,從而允許函數(shù)在知道需要強(qiáng)制執(zhí)行的特定類(lèi)型之前靈活地強(qiáng)制執(zhí)行類(lèi)型結(jié)構(gòu)。 當(dāng)在 Visual Studio Code 等 IDE 中使用該函數(shù)時(shí),這使得開(kāi)發(fā)體驗(yàn)更好,它將根據(jù)提供的對(duì)象為 keys
參數(shù)提供建議:
2. 在函數(shù)中使用泛型
將泛型與函數(shù)一起使用的最常見(jiàn)場(chǎng)景之一就是,當(dāng)有一些不容易為所有的用例定義類(lèi)型時(shí),為了使該函數(shù)適用于更多情況,就可以使用泛型來(lái)定義。下面來(lái)看看在函數(shù)中使用泛型的常見(jiàn)場(chǎng)景。
(1)分配泛型參數(shù)
先來(lái)看下面的函數(shù),它返回函數(shù)參數(shù)傳入的內(nèi)容:
function identity(value) { return value; }
可以為其添加泛型類(lèi)型以使函數(shù)的類(lèi)型更安全:
function identity<T>(value: T): T { return value; }
這里將函數(shù)轉(zhuǎn)化為接受泛型類(lèi)型參數(shù) T
的泛型函數(shù),它第一個(gè)參數(shù)的類(lèi)型,然后將返回類(lèi)型也設(shè)置為 T
。
下面來(lái)測(cè)試一下這個(gè)函數(shù):
function identity<T>(value: T): T { return value; } const result = identity(123);
result
的類(lèi)型為 123
,這是我們傳入的數(shù)字:
此時(shí),TypeScript 使用調(diào)用代碼本身來(lái)推斷泛型類(lèi)型。這樣調(diào)用代碼不需要傳遞任何類(lèi)型參數(shù)。
當(dāng)然,我們也可以顯式地將泛型類(lèi)型參數(shù)設(shè)置為想要的類(lèi)型:
function identity<T>(value: T): T { return value; } const result = identity<number>(123);
在這段代碼中,result
的類(lèi)型就是 number
:
這里使用 <number>
定義了傳入類(lèi)型,讓 TypeScript 標(biāo)識(shí)函數(shù)的泛型類(lèi)型參數(shù) T
為 number
類(lèi)型。 這將強(qiáng)制number
類(lèi)型作為參數(shù)和返回值的類(lèi)型。
當(dāng)再傳入其他類(lèi)型時(shí),就會(huì)報(bào)錯(cuò):
(2)直接傳遞類(lèi)型參數(shù)
在使用自定義類(lèi)型時(shí),直接傳遞類(lèi)型參數(shù)也很有用。
來(lái)看下面的代碼:
type ProgrammingLanguage = { name: string; }; function identity<T>(value: T): T { return value; } const result = identity<ProgrammingLanguage>({ name: "TypeScript" });
在這段代碼中,result
為自定義類(lèi)型 ProgrammingLanguage
,它直接傳遞給了 identity
函數(shù)。 如果沒(méi)有顯式地定義類(lèi)型參數(shù),則result
的類(lèi)型就是 { name: string }
。
另一個(gè)常見(jiàn)的例子就是使用函數(shù)從 API 獲取數(shù)據(jù):
async function fetchApi(path: string) { const response = await fetch(`https://example.com/api${path}`) return response.json(); }
這個(gè)異步函數(shù)將 URL 路徑path
作為參數(shù),使用 fetch API 向 URL 發(fā)出請(qǐng)求,然后返回 JSON 響應(yīng)值。在這種情況下,fetchApi
函數(shù)的返回類(lèi)型是 Promise<any>
,這是 fetch
的響應(yīng)對(duì)象的 json()
調(diào)用的返回類(lèi)型。
將 any
作為返回類(lèi)型并不會(huì)有任何作用,它表示任意類(lèi)型,使用它將失去靜態(tài)類(lèi)型檢查。如果我們知道 API 將返回指定結(jié)構(gòu)的對(duì)象,則可以使用泛型以使此函數(shù)類(lèi)型更安全:
async function fetchApi<ResultType>(path: string): Promise<ResultType> { const response = await fetch(`https://example.com/api${path}`); return response.json(); }
這里就將函數(shù)轉(zhuǎn)換為接受 ResultType
泛型類(lèi)型參數(shù)的泛型函數(shù)。 此泛型類(lèi)型用于函數(shù)的返回類(lèi)型:Promise<ResultType>
。
注意:由于這個(gè)函數(shù)是異步的,因此會(huì)返回一個(gè) Promise 對(duì)象。 TypeScript 中的 Promise 類(lèi)型本身是一個(gè)泛型類(lèi)型,它接受 Promise 解析為的值的類(lèi)型。
可以看到,泛型并沒(méi)有在參數(shù)列表中使用,也沒(méi)有在TypeScript能夠推斷其值的其他地方使用。這意味著在調(diào)用函數(shù)時(shí),必須顯式地傳遞此泛型的類(lèi)型:
type User = { name: string; } async function fetchApi<ResultType>(path: string): Promise<ResultType> { const response = await fetch(`https://example.com/api${path}`); return response.json(); } const data = await fetchApi<User[]>('/users')
在這段代碼中,創(chuàng)建了一個(gè)名為 User
的新類(lèi)型,并使用該類(lèi)型的數(shù)組 (User[]
) 作為 ResultType
泛型參數(shù)的類(lèi)型。data
變量現(xiàn)在的類(lèi)型是 User[]
而不是 any
。
注意:當(dāng)使用 await 異步處理函數(shù)的結(jié)果時(shí),返回類(lèi)型將是
Promise<T>
中的 T 類(lèi)型,在這個(gè)示例中就是泛型類(lèi)型ResultType
。
(3)默認(rèn)類(lèi)型參數(shù)
在上面 fetchApi
函數(shù)的例子中,調(diào)用代碼時(shí)必須提供類(lèi)型參數(shù)。 如果調(diào)用代碼不包含泛型類(lèi)型參數(shù),則 ResultType
將推斷為 unknow
。來(lái)看下面的例子:
async function fetchApi<ResultType>(path: string): Promise<ResultType> { const response = await fetch(`https://example.com/api${path}`); return response.json(); } const data = await fetchApi('/users') console.log(data.a)
這段代碼嘗試訪(fǎng)問(wèn)data
的a
屬性,但是由于data
是unknow
類(lèi)型,將無(wú)法訪(fǎng)問(wèn)對(duì)象的屬性。
如果不打算為泛型函數(shù)的每次調(diào)用添加特定的類(lèi)型,則可以為泛型類(lèi)型參數(shù)添加默認(rèn)類(lèi)型。通過(guò)在泛型類(lèi)型參數(shù)后面添加 = DefaultType
來(lái)完成:
async function fetchApi<ResultType = Record<string, any>>(path: string): Promise<ResultType> { const response = await fetch(`https://example.com/api${path}`); return response.json(); } const data = await fetchApi('/users') console.log(data.a)
這里不需要在調(diào)用 fetchApi
函數(shù)時(shí)將類(lèi)型傳遞給 ResultType
泛型參數(shù),因?yàn)樗哂心J(rèn)類(lèi)型 Record<string, any>
。 這意味著 TypeScript 會(huì)將data
識(shí)別為具有string
類(lèi)型的鍵和any
類(lèi)型值的對(duì)象,從而允許訪(fǎng)問(wèn)其屬性。
(4)類(lèi)型參數(shù)約束
在某些情況下,泛型類(lèi)型參數(shù)只允許將某些類(lèi)型傳遞到泛型中,這時(shí)就可以對(duì)參數(shù)添加約束。
假如有一個(gè)存儲(chǔ)限制,只能存儲(chǔ)所有屬性值都為字符串類(lèi)型的對(duì)象。 因此,可以創(chuàng)建一個(gè)函數(shù),該函數(shù)接受任何對(duì)象并返回另一個(gè)對(duì)象,其 key
值與原始對(duì)象相同,但所有值都轉(zhuǎn)換為字符串。
代碼如下:
function stringifyObjectKeyValues<T extends Record<string, any>>(obj: T) { return Object.keys(obj).reduce((acc, key) => ({ ...acc, [key]: JSON.stringify(obj[key]) }), {} as { [K in keyof T]: string }) }
在這段代碼中,stringifyObjectKeyValues
函數(shù)使用 reduce
數(shù)組方法遍歷包含原始對(duì)象的key
的數(shù)組,將屬性值字符串化并將它們添加到新數(shù)組中。
為確保調(diào)用代碼始終傳入一個(gè)對(duì)象作為參數(shù),可以在泛型類(lèi)型 T
上使用類(lèi)型約束:
function stringifyObjectKeyValues<T extends Record<string, any>>(obj: T) { // ... }
extends Record<string, any>
被稱(chēng)為泛型類(lèi)型約束,它允許指定泛型類(lèi)型必須可分配給 extends
關(guān)鍵字之后的類(lèi)型。 在這種情況下,Record<string, any>
表示具有string
類(lèi)型的鍵和any
類(lèi)型的值的對(duì)象。 我們可以使類(lèi)型參數(shù)擴(kuò)展任何有效的 TypeScript 類(lèi)型。
在調(diào)用reduce
時(shí),reducer
函數(shù)的返回類(lèi)型是基于累加器的初始值。 {} as { [K in keyof T]: string }
通過(guò)對(duì)空對(duì)象 {}
使用類(lèi)型斷言將累加器的初始值的類(lèi)型設(shè)置為{ [K in keyof T]: string }
。 type { [K in keyof T]: string }
創(chuàng)建了一個(gè)新類(lèi)型,其鍵與 T 相同,但所有值都設(shè)置為字符串類(lèi)型,這稱(chēng)為映射類(lèi)型。
下面來(lái)測(cè)試一下這個(gè)函數(shù):
function stringifyObjectKeyValues<T extends Record<string, any>>(obj: T) { return Object.keys(obj).reduce((acc, key) => ({ ...acc, [key]: JSON.stringify(obj[key]) }), {} as { [K in keyof T]: string }) } const stringifiedValues = stringifyObjectKeyValues({ a: "1", b: 2, c: true, d: [1, 2, 3]})
變量 stringifiedValues
的類(lèi)型如下:
{ a: string; b: string; c: string; d: string; }
3. 在接口、類(lèi)和類(lèi)型中使用泛型
在 TypeScript 中創(chuàng)建接口和類(lèi)時(shí),使用泛型類(lèi)型參數(shù)來(lái)設(shè)置結(jié)果對(duì)象的類(lèi)型會(huì)很有用。 例如,一個(gè)類(lèi)可能具有不同類(lèi)型的屬性,具體取決于傳入構(gòu)造函數(shù)的內(nèi)容。 下面就來(lái)看看在類(lèi)和接口中聲明泛型類(lèi)型參數(shù)的語(yǔ)法。
(1)接口和類(lèi)中的泛型
要?jiǎng)?chuàng)建泛型接口,可以在接口名稱(chēng)后添加類(lèi)型參數(shù)列表:
interface MyInterface<T> { field: T }
這里聲明了一個(gè)具有field
字段的接口,field
字段的類(lèi)型由傳入 T
的類(lèi)型確定。
對(duì)于類(lèi),它的語(yǔ)法和接口定義幾乎是相同的:
class MyClass<T> { field: T constructor(field: T) { this.field = field } }
通用接口/類(lèi)的一個(gè)常見(jiàn)例子就是當(dāng)有一個(gè)類(lèi)型取決于如何使用接口/類(lèi)的字段。 假設(shè)有一個(gè) HttpApplication
類(lèi),用于處理對(duì) API 的 HTTP 請(qǐng)求,并且某些 context
值將被傳遞給每個(gè)請(qǐng)求處理程序。
代碼如下:
class HttpApplication<Context> { context: Context constructor(context: Context) { this.context = context; } // ... get(url: string, handler: (context: Context) => Promise<void>): this { // ... return this; } }
這個(gè)類(lèi)儲(chǔ)存了一個(gè) context
,它的類(lèi)型作為 get
方法中handler
函數(shù)的參數(shù)類(lèi)型傳入。 在使用時(shí),傳遞給 get
方法的handler
的參數(shù)類(lèi)型將從傳遞給類(lèi)構(gòu)造函數(shù)的內(nèi)容中推斷出來(lái):
const context = { someValue: true }; const app = new HttpApplication(context); app.get('/api', async () => { console.log(context.someValue) });
在這段代碼中,TypeScript 會(huì)將 context.someValue
的類(lèi)型推斷為 boolean
。
(2)自定義類(lèi)型中的泛型
將泛型應(yīng)用于類(lèi)型的語(yǔ)法類(lèi)似于它們應(yīng)用于接口和類(lèi)的方式。
來(lái)看下面的代碼:
type MyIdentityType<T> = T
這個(gè)泛型類(lèi)型返回類(lèi)型參數(shù)傳遞的類(lèi)型。
使用以下代碼來(lái)實(shí)現(xiàn)這種類(lèi)型:
type B = MyIdentityType<number>
在這種情況下,B
的類(lèi)型就是number
。
泛型類(lèi)型通常用于創(chuàng)建工具類(lèi)型,尤其是在使用映射類(lèi)型時(shí)。 TypeScript 內(nèi)置了許多工具類(lèi)型。 例如 Partial
實(shí)用工具類(lèi)型,它傳入類(lèi)型 T
并返回另一種具有與 T
相同的類(lèi)型,但它們的所有字段都設(shè)置為可選。 Partial
的實(shí)現(xiàn)如下:
type Partial<T> = { [P in keyof T]?: T[P]; };
這里,Partial
接受一個(gè)類(lèi)型,遍歷它的屬性類(lèi)型,然后將它們作為可選的新類(lèi)型返回。
注意:由于 Partial 已經(jīng)內(nèi)置到了 TypeScript 中,因此將此代碼編譯到 TypeScript 環(huán)境中會(huì)重新聲明 Partial 并引發(fā)錯(cuò)誤。 上面的 Partial 實(shí)現(xiàn)僅用于說(shuō)明目的。
4. 使用泛型創(chuàng)建映射類(lèi)型
使用 TypeScript 時(shí),有時(shí)需要?jiǎng)?chuàng)建一個(gè)與另一種類(lèi)型具有相同結(jié)構(gòu)的類(lèi)型。這意味著它們應(yīng)該具有相同的屬性,但屬性的類(lèi)型不同。對(duì)于這種情況,使用映射類(lèi)型可以重用初始類(lèi)型并減少重復(fù)代碼。這種結(jié)構(gòu)稱(chēng)為映射類(lèi)型并依賴(lài)于泛型。下面就來(lái)看看如何創(chuàng)建映射類(lèi)型。
先來(lái)看一個(gè)例子,給定一種類(lèi)型,返回一個(gè)新類(lèi)型,其中所有屬性值都設(shè)置為 boolean
類(lèi)型。
可以使用以下代碼創(chuàng)建此類(lèi)型:
type BooleanFields<T> = { [K in keyof T]: boolean; }
在這種類(lèi)型中,使用 [K in keyof T]
指定新類(lèi)型將具有的屬性。keyof T
用于返回 T
中所有可用屬性的名稱(chēng)。然后使用 K in
來(lái)指定新類(lèi)型的屬性是keyof T
返回的類(lèi)型中可用的所有屬性。
這將創(chuàng)建一個(gè)名為 K
的新類(lèi)型,該類(lèi)型就是當(dāng)前屬性的名稱(chēng)。 可以用于使用 T[K]
語(yǔ)法來(lái)訪(fǎng)問(wèn)原始類(lèi)型中此屬性的類(lèi)型。 在這種情況下,將屬性值的類(lèi)型設(shè)置為 boolean
。
這種 BooleanFields
類(lèi)型的一個(gè)使用場(chǎng)景是創(chuàng)建一個(gè)選項(xiàng)對(duì)象。 假設(shè)有一個(gè)數(shù)據(jù)庫(kù)模型,例如 User。 從數(shù)據(jù)庫(kù)中獲取此模型的記錄時(shí),還可以傳遞一個(gè)指定要返回哪些字段的對(duì)象。 該對(duì)象將具有與模型相同的屬性,但類(lèi)型設(shè)置為布爾值。 在字段中傳遞 true
意味著希望它被返回,而 false 則意味著希望它被省略。
可以在現(xiàn)有模型類(lèi)型上使用 BooleanFields
泛型來(lái)返回與模型具有相同結(jié)構(gòu)的新類(lèi)型,但所有字段都設(shè)置為布爾類(lèi)型,
代碼如下所示:
type BooleanFields<T> = { [K in keyof T]: boolean; }; type User = { email: string; name: string; } type UserFetchOptions = BooleanFields<User>;
UserFetchOptions
的類(lèi)型如下:
type UserFetchOptions = { email: boolean; name: boolean; }
在創(chuàng)建映射類(lèi)型時(shí),還可以為字段提供修飾符,例如 Readonly<T>
。 Readonly<T>
類(lèi)型返回一個(gè)新類(lèi)型,其中傳遞類(lèi)型的所有屬性都設(shè)置為只讀屬性。這種類(lèi)型的實(shí)現(xiàn)如下:
type Readonly<T> = { readonly [K in keyof T]: T[K] }
注意:由于 Readonly 已經(jīng)內(nèi)置到 TypeScript 中,因此將此代碼編譯到您的 TypeScript 環(huán)境中會(huì)重新聲明 Readonly 并引發(fā)錯(cuò)誤。 此處引用的 Readonly 實(shí)現(xiàn)僅用于說(shuō)明目的。
目前,可以在映射類(lèi)型中使用的兩個(gè)可用修飾符是 readonly
修飾符,它必須作為前綴添加到屬性中,用于將屬性設(shè)置為只讀;以及?
修飾符,可以作為后綴添加到屬性中,用于將屬性設(shè)置為可選。
5. 使用泛型創(chuàng)建條件類(lèi)型
下面來(lái)看看如何使用泛型創(chuàng)建條件類(lèi)型。
(1)基礎(chǔ)條件類(lèi)型
條件類(lèi)型是泛型類(lèi)型,根據(jù)某些條件具有不同的結(jié)果類(lèi)型。 先來(lái)看看下面的泛型類(lèi)型 IsStringType<T>
:
type IsStringType<T> = T extends string ? true : false;
在這段代碼中,創(chuàng)建了一個(gè)名為 IsStringType
的新泛型類(lèi)型,它接收一個(gè)類(lèi)型參數(shù) T
。在類(lèi)型定義中,使用的語(yǔ)法類(lèi)似于 JavaScript 中的三元表達(dá)式。此條件表達(dá)式檢查類(lèi)型 T
是否是 string
類(lèi)型。 如果是,結(jié)果的類(lèi)型將是 true
; 否則,結(jié)果的類(lèi)型將是 false
。
要嘗試這種條件類(lèi)型,需要將類(lèi)型作為其類(lèi)型參數(shù)傳遞:
type IsStringType<T> = T extends string ? true : false; type A = "abc"; type B = { name: string; }; type ResultA = IsStringType<A>; type ResultB = IsStringType<B>;
在此代碼中,創(chuàng)建了兩種類(lèi)型:A
和 B
。A
是字符串字面量類(lèi)型 abc
,B
是具有string
類(lèi)型的name
屬性的對(duì)象的類(lèi)型。將這兩種類(lèi)型與 IsStringType
條件類(lèi)型一起使用,并將結(jié)果類(lèi)型存儲(chǔ)到兩個(gè)新類(lèi)型ResultA
和ResultB
中。
這里ResultA
類(lèi)型設(shè)置為 true
,而 ResultB 類(lèi)型設(shè)置為 false
。 因?yàn)?nbsp;A
確實(shí)擴(kuò)展了字符串類(lèi)型,而 B 沒(méi)有擴(kuò)展字符串類(lèi)型,因?yàn)樗辉O(shè)置為具有字符串類(lèi)型的單個(gè)name
屬性的對(duì)象的類(lèi)型。
條件類(lèi)型的一個(gè)有用特性是它允許使用特殊關(guān)鍵字infer
在extends
中推斷類(lèi)型信息。 可以在條件為真的分支中使用這種新類(lèi)型。 此功能的一種用途是檢索任何函數(shù)類(lèi)型的返回類(lèi)型。
例如,GetReturnType
類(lèi)型定義如下:
type GetReturnType<T> = T extends (...args: any[]) => infer U ? U : never;
在這段代碼中,創(chuàng)建了一個(gè)新的泛型類(lèi)型,它是一個(gè)名為GetReturnType
的條件類(lèi)型。 此泛型類(lèi)型接受一個(gè)類(lèi)型參數(shù) T
。在類(lèi)型聲明本身內(nèi)部,檢查類(lèi)型T
是否擴(kuò)展了與接受可變數(shù)量參數(shù)(包括0)的函數(shù)簽名匹配的類(lèi)型,然后推斷該返回函數(shù)的類(lèi)型,創(chuàng)建一個(gè)新類(lèi)型 U
,它可用于條件的真實(shí)分支。 U
的類(lèi)型將綁定到傳遞函數(shù)的返回值的類(lèi)型。 如果傳遞的類(lèi)型 T
不是函數(shù),則代碼將返回類(lèi)型nerver
。
將此類(lèi)型與以下代碼一起使用:
type GetReturnType<T> = T extends (...args: any[]) => infer U ? U : never; function someFunction() { return true; } type ReturnTypeOfSomeFunction = GetReturnType<typeof someFunction>;
在這段代碼中,創(chuàng)建了一個(gè)名為 someFunction
的函數(shù),該函數(shù)返回 true
。 然后,使用 typeof
運(yùn)算符將此函數(shù)的類(lèi)型傳遞給 GetReturnType
泛型,并將結(jié)果類(lèi)型保存在 ReturnTypeOfSomeFunction
中。
由于 someFunction
變量的類(lèi)型是函數(shù),因此條件類(lèi)型將計(jì)算條件為真的分支。這將返回類(lèi)型 U
作為結(jié)果。 U
類(lèi)型是根據(jù)函數(shù)的返回類(lèi)型推斷出來(lái)的,在本例中是boolean
。 如果檢查 ReturnTypeOfSomeFunction
的類(lèi)型,會(huì)發(fā)現(xiàn)它被設(shè)置為了boolean
類(lèi)型。
(2)高級(jí)條件類(lèi)型
條件類(lèi)型是 TypeScript 中最靈活的功能之一,允許創(chuàng)建一些高級(jí)實(shí)用程序類(lèi)型。接下來(lái)就創(chuàng)建一個(gè)名為 NestedOmit<T, KeysToOmit>
的類(lèi)型,它可以省略對(duì)象中的字段,就像現(xiàn)有的 Omit<T, KeysToOmit>
實(shí)用程序類(lèi)型一樣,但也允許使用點(diǎn)表示法省略嵌套字段。
使用新的 NestedOmit<T, KeysToOmit>
泛型,將能夠使用下面例子中的類(lèi)型:
type SomeType = { a: { b: string, c: { d: number; e: string[] }, f: number } g: number | string, h: { i: string, j: number, }, k: { l: number,<F3> } } type Result = NestedOmit<SomeType, "a.b" | "a.c.e" | "h.i" | "k">;
這段代碼聲明了一個(gè)名為 SomeType
的類(lèi)型,該類(lèi)型具有嵌套屬性的多級(jí)結(jié)構(gòu)。 使用 NestedOmit
泛型傳入類(lèi)型,然后列出想要省略的屬性的key
。 第二個(gè)類(lèi)型參數(shù)中使用點(diǎn)符號(hào)來(lái)標(biāo)識(shí)要省略的鍵。 然后將結(jié)果類(lèi)型存儲(chǔ)在 Result
中。
構(gòu)造此條件類(lèi)型將使用 TypeScript 中的許很多功能,例如模板文本類(lèi)型、泛型、條件類(lèi)型和映射類(lèi)型。
首先創(chuàng)建一個(gè)名為 NestedOmit
的泛型類(lèi)型,它接受兩個(gè)類(lèi)型參數(shù):
type NestedOmit<T extends Record<string, any>, KeysToOmit extends string>
第一個(gè)類(lèi)型參數(shù)為 T
,它必須是可分配給 Record<string, any>
類(lèi)型的類(lèi)型,它是要從中省略屬性的對(duì)象的類(lèi)型。 第二個(gè)類(lèi)型參數(shù)為 KeysToOmit
,它必須是string
類(lèi)型。 使用它來(lái)指定要從類(lèi)型 T
中省略的key
。
接下來(lái)需要判斷 KeysToOmit
是否可分配給類(lèi)型 ${infer KeyPart1}.${infer KeyPart2}:
type NestedOmit<T extends Record<string, any>, KeysToOmit extends string> = KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}`
這里就用到了模板文本類(lèi)型,同時(shí)利用條件類(lèi)型在模板文字中推斷出其他兩種類(lèi)型。 通過(guò)這兩部分,將一個(gè)字符串拆分為了兩個(gè)字符串。 第一部分將分配給類(lèi)型 KeyPart1
并將包含第一個(gè)點(diǎn)之前的所有內(nèi)容。 第二部分將分配給類(lèi)型 KeyPart2
并將包含第一個(gè)點(diǎn)之后的所有內(nèi)容。 如果將“a.b.c
”作為 KeysToOmit
傳遞,則最初 KeyPart1
將設(shè)置為字符串類(lèi)型“a
”,而 KeyPart2
將設(shè)置為“b.c
”。
接下來(lái),使用三元表達(dá)式來(lái)定義條件為true
的分支:
type NestedOmit<T extends Record<string, any>, KeysToOmit extends string> = KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}` ? KeyPart1 extends keyof T
這里使用 KeyPart1 extends keyof T
來(lái)檢查 KeyPart1
是否是給定類(lèi)型 T
的有效屬性。如果是一個(gè)有效的 key
,使用以下代碼以使條件計(jì)算為兩種類(lèi)型之間的交集:
type NestedOmit<T extends Record<string, any>, KeysToOmit extends string> = KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}` ? KeyPart1 extends keyof T ? Omit<T, KeyPart1> & { [NewKeys in KeyPart1]: NestedOmit<T[NewKeys], KeyPart2> }
Omit<T, KeyPart1>
是使用 TypeScript 自帶的 Omit
構(gòu)建的類(lèi)型。 此時(shí),KeyPart1
不是點(diǎn)表示法:它將包含一個(gè)字段的確切名稱(chēng),該字段包含要從原始類(lèi)型中省略的嵌套字段。因此,可以安全地使用現(xiàn)有的實(shí)用程序類(lèi)型。
使用 Omit
刪除 T[KeyPart1]
內(nèi)的一些嵌套字段,為此,必須重新生成T[KeyPart1]
的類(lèi)型。 為避免重新生成整個(gè) T
類(lèi)型,使用 Omit
從 T
中僅刪除 KeyPart1
,保留其他字段。 然后,將在下一部分的類(lèi)型中重建 T[KeyPart1]
。
[NewKeys in KeyPart1]:NestedOmit<T[NewKeys], KeyPart2>是一個(gè)映射類(lèi)型,其中屬性是可分配給KeyPart1
的屬性,也就是上面從KeysToOmit
中提取的部分。 這是需要?jiǎng)h除的字段的父級(jí)。 如果傳入a.b.c
,那么在第一次它將是a
中的 NewKeys
。 然后,將此屬性的類(lèi)型設(shè)置為遞歸調(diào)用NestedOmit
實(shí)用程序類(lèi)型的結(jié)果,但現(xiàn)在使用T[NewKeys]
作為第一個(gè)類(lèi)型參數(shù)傳遞 T 內(nèi)的此屬性的類(lèi)型,并作為第二個(gè)類(lèi)型參數(shù)傳遞點(diǎn)符號(hào)的其余key
,在 KeyPart2
中可用。
在內(nèi)部條件為 false
分支中,返回綁定到 T 的當(dāng)前類(lèi)型,就好像 KeyPart1
不是T
的有效key
:
type NestedOmit<T extends Record<string, any>, KeysToOmit extends string> = KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}` ? KeyPart1 extends keyof T ? Omit<T, KeyPart1> & { [NewKeys in KeyPart1]: NestedOmit<T[NewKeys], KeyPart2> } : T
條件的這個(gè)分支意味著省略T
中不存在的字段。在這種情況下,無(wú)需再進(jìn)一步。最后,在外部條件為 false
的分支中,使用內(nèi)置的 Omit
實(shí)用程序類(lèi)型從T
中省略 KeysToOmit
:
type NestedOmit<T extends Record<string, any>, KeysToOmit extends string> = KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}` ? KeyPart1 extends keyof T ? Omit<T, KeyPart1> & { [NewKeys in KeyPart1]: NestedOmit<T[NewKeys], KeyPart2> } : T : Omit<T, KeysToOmit>;
如果條件KeysToOmit extends
{infer KeyPart1}.inferKeyPart1.{infer KeyPart2}`` 為 false
,則表示KeysToOmit
未使用點(diǎn)表示法,因此可以使用Omit
實(shí)用程序類(lèi)型。
現(xiàn)在,要使用新的NestedOmit
條件類(lèi)型,創(chuàng)建一個(gè)名為NestedObject
的類(lèi)型:
type NestedObject = { a: { b: { c: number; d: number; }; e: number; }; f: number; };
調(diào)用 NestedOmit
以省略 a.b.c
中可用的嵌套字段:
type Result = NestedOmit<NestedObject, "a.b.c">;
在條件類(lèi)型的第一次計(jì)算中,外部條件為真,因?yàn)樽址置媪款?lèi)型a.b.c
可分配給模板文本類(lèi)型${infer KeyPart1}.${infer KeyPart2}。 在這種情況下,KeyPart1
將被推斷為字符串字面量類(lèi)型a
,而 KeyPart2
將被推斷為字符串的剩余部分,在本例中為b.c
。
下面將計(jì)算內(nèi)部條件,結(jié)果為真,因?yàn)榇藭r(shí)KeyPart1
是T
的鍵。 KeyPart1
現(xiàn)在是a
,并且T
確實(shí)具有屬性a
:
type NestedObject = { a: { b: { c: number; d: number; }; e: number; }; f: number; };
繼續(xù)計(jì)算條件,現(xiàn)在位于內(nèi)部 true
分支中。這構(gòu)建了一個(gè)新類(lèi)型,它是其他兩種類(lèi)型的交集。第一種類(lèi)型是在 T
上使用 Omit
實(shí)用程序類(lèi)型來(lái)省略可分配給 KeyPart1
的字段(在本例中為 a
字段)的結(jié)果。第二種類(lèi)型是通過(guò)遞歸調(diào)用 NestedOmit
構(gòu)建的新類(lèi)型。
如果對(duì)NestedOmit
進(jìn)行下一步求值,對(duì)于第一次遞歸調(diào)用,交集類(lèi)型會(huì)構(gòu)建一個(gè)類(lèi)型以用作a
字段的類(lèi)型。這將重新創(chuàng)建a
字段,而不需要忽略嵌套字段。
在NestedOmit
的最終計(jì)算中,第一個(gè)條件將返回false
,因?yàn)閭鬟f的字符串類(lèi)型是c
。這種情況下,可以使用內(nèi)置類(lèi)型從對(duì)象中省略該字段。這將返回 b
字段的類(lèi)型,即省略c
的原始類(lèi)型。計(jì)算到此結(jié)束,TypeScript 返回了需要使用的新類(lèi)型,省略了嵌套字段。
6. 小結(jié)
本文詳細(xì)解釋了適用于函數(shù)、接口、類(lèi)和自定義類(lèi)型的泛型,還使用泛型創(chuàng)建映射類(lèi)型和條件類(lèi)型。 這些中的每一個(gè)都使泛型成為使用 TypeScript 時(shí)的強(qiáng)大工具。 正確使用它們將避免一遍又一遍地重復(fù)代碼,并使編寫(xiě)的類(lèi)型更加靈活。
到此這篇關(guān)于在 TypeScript 中使用泛型的方法的文章就介紹到這了,更多相關(guān)TypeScript 泛型內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Javascript百度地圖接口開(kāi)發(fā)文檔中的類(lèi)和方法
最近在工作中在用laravel框架仿寫(xiě)?zhàn)I了么外賣(mài)商城,于是學(xué)習(xí)了一下有關(guān)地圖接口相關(guān)的知識(shí),以下是百步地圖接口開(kāi)發(fā)文檔的一些類(lèi)和方法的使用,需要的朋友們可以參考借鑒,下面來(lái)一起看看吧。2017-02-02JavaScript獲取Excel表格的列序號(hào)和列名
這篇文章主要介紹了JavaScript獲取Excel表格的列序號(hào)和列名,在處理Excel文件時(shí),通常要獲取xx列的數(shù)據(jù),這就要求先找到列序號(hào),下文關(guān)于列名獲取需要的小伙伴可以參考一下2022-05-05簡(jiǎn)單實(shí)現(xiàn)IONIC購(gòu)物車(chē)功能
這篇文章主要為大家詳細(xì)介紹了IONIC簡(jiǎn)易購(gòu)物車(chē)的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01Javascript string 擴(kuò)展庫(kù)代碼
Javascript原生的String處理函數(shù)顯得很不夠豐富2010-04-04Firefox/Chrome/Safari的中可直接使用$/$$函數(shù)進(jìn)行調(diào)試
偶然發(fā)現(xiàn)的,頁(yè)面中沒(méi)有引入Prototype和jQuery。控制臺(tái)中敲$卻發(fā)現(xiàn)是一個(gè)函數(shù)。又試著敲$$,也是個(gè)function2012-02-02一篇文章搞定JavaScript類(lèi)型轉(zhuǎn)換(面試常見(jiàn))
這篇文章主要介紹了一篇文章搞定JavaScript類(lèi)型轉(zhuǎn)換(面試常見(jiàn)),非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-01-01Javascript使用function創(chuàng)建類(lèi)的兩種方法(推薦)
下面小編就為大家?guī)?lái)一篇Javascript使用function創(chuàng)建類(lèi)的兩種方法(推薦)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-11-11JavaScript中數(shù)組雙重去重的方法總結(jié)
這篇文章主要為大家學(xué)習(xí)介紹了JavaScript中數(shù)組雙重去重的幾個(gè)常用方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-07-07