掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢(xún)/運(yùn)營(yíng)咨詢(xún)/技術(shù)建議/互聯(lián)網(wǎng)交流
泛型是靜態(tài)類(lèi)型語(yǔ)言的基本特征,允許開(kāi)發(fā)人員將類(lèi)型作為參數(shù)傳遞給另一種類(lèi)型、函數(shù)或其他結(jié)構(gòu)。當(dāng)開(kāi)發(fā)人員使他們的組件成為通用組件時(shí),他們使該組件能夠接受和強(qiáng)制在使用組件時(shí)傳入的類(lèi)型,這提高了代碼靈活性,使組件可重用并消除重復(fù)。

TypeScript 完全支持泛型,以此將類(lèi)型安全性引入到接受參數(shù)和返回值的組件中,這些參數(shù)和返回值的類(lèi)型,在稍后的代碼中使用之前是不確定的。在今天的內(nèi)容中,我們將嘗試 TypeScript 泛型的真實(shí)示例,并探索它們?nèi)绾卧诤瘮?shù)、類(lèi)型、類(lèi)和接口中使用。
我們還將使用泛型創(chuàng)建映射類(lèi)型和條件類(lèi)型,這將幫助我們創(chuàng)建可以靈活應(yīng)用于代碼中所有必要情況的 TypeScript 組件。
TypeScript 是 JavaScript 語(yǔ)言的擴(kuò)展,它使用 JavaScript 運(yùn)行時(shí)和編譯時(shí)類(lèi)型檢查器。
TypeScript 提供了多種方法來(lái)表示代碼中的對(duì)象,其中一種是使用接口。 TypeScript 中的接口有兩種使用場(chǎng)景:您可以創(chuàng)建類(lèi)必須遵循的約定,例如,這些類(lèi)必須實(shí)現(xiàn)的成員,還可以在應(yīng)用程序中表示類(lèi)型,就像普通的類(lèi)型聲明一樣。
您可能會(huì)注意到接口和類(lèi)型共享一組相似的功能。
事實(shí)上,一個(gè)幾乎總是可以替代另一個(gè)。
主要區(qū)別在于接口可能對(duì)同一個(gè)接口有多個(gè)聲明,TypeScript 將合并這些聲明,而類(lèi)型只能聲明一次。您還可以使用類(lèi)型來(lái)創(chuàng)建原始類(lèi)型(例如字符串和布爾值)的別名,這是接口無(wú)法做到的。
TypeScript 中的接口是表示類(lèi)型結(jié)構(gòu)的強(qiáng)大方法。它們?cè)试S您以類(lèi)型安全的方式使用這些結(jié)構(gòu)并同時(shí)記錄它們,從而直接改善開(kāi)發(fā)人員體驗(yàn)。
在今天的文章中,我們將在 TypeScript 中創(chuàng)建接口,學(xué)習(xí)如何使用它們,并了解普通類(lèi)型和接口之間的區(qū)別。
我們將嘗試不同的代碼示例,可以在 TypeScript 環(huán)境或 TypeScript Playground(一個(gè)允許您直接在瀏覽器中編寫(xiě) TypeScript 的在線環(huán)境)中遵循這些示例。
要完成今天的示例,我們將需要做如下準(zhǔn)備工作:
本教程中顯示的所有示例都是使用 TypeScript 4.2.3 版創(chuàng)建的。
在進(jìn)入泛型應(yīng)用之前,本教程將首先介紹 TypeScript 泛型的語(yǔ)法,然后通過(guò)一個(gè)示例來(lái)說(shuō)明它們的一般用途。
泛型出現(xiàn)在尖括號(hào)內(nèi)的 TypeScript 代碼中,格式為
在這種情況下,T 將以與函數(shù)中參數(shù)相同的方式運(yùn)行,作為將在創(chuàng)建結(jié)構(gòu)實(shí)例時(shí)聲明的類(lèi)型的占位符。因此,尖括號(hào)內(nèi)指定的泛型類(lèi)型也稱(chēng)為泛型類(lèi)型參數(shù)或只是類(lèi)型參數(shù)。多個(gè)泛型類(lèi)型也可以出現(xiàn)在單個(gè)定義中,例如
注意:按照慣例,程序員通常使用單個(gè)字母來(lái)命名泛型類(lèi)型。這不是語(yǔ)法規(guī)則,你可以像 TypeScript 中的任何其他類(lèi)型一樣命名泛型,但這種約定有助于立即向那些閱讀你的代碼的人傳達(dá)泛型類(lèi)型不需要特定類(lèi)型。
泛型可以出現(xiàn)在函數(shù)、類(lèi)型、類(lèi)和接口中。本教程稍后將介紹這些結(jié)構(gòu)中的每一個(gè),但現(xiàn)在將使用一個(gè)函數(shù)作為示例來(lái)說(shuō)明泛型的基本語(yǔ)法。
要了解泛型有多么有用,假設(shè)您有一個(gè) JavaScript 函數(shù),它接受兩個(gè)參數(shù):一個(gè)對(duì)象和一個(gè)鍵數(shù)組。該函數(shù)將基于原始對(duì)象返回一個(gè)新對(duì)象,但僅包含您想要的鍵:
function pickObjectKeys(obj, keys) {
let result = {}
for (const key of keys) {
if (key in obj) {
result[key] = obj[key]
}
}
return result
}此代碼段顯示了 pickObjectKeys() 函數(shù),該函數(shù)遍歷keys數(shù)組并使用數(shù)組中指定的鍵創(chuàng)建一個(gè)新對(duì)象。
下面是一個(gè)展示如何使用該函數(shù)的示例:
const language = {
name: "TypeScript",
age: 8,
extensions: ['ts', 'tsx']
}
const ageAndExtensions = pickObjectKeys(language, ['age', 'extensions'])這聲明了一種對(duì)象,然后使用 pickObjectKeys() 函數(shù)隔離 age 和 extensions 屬性。 ageAndExtensions 的值如下:
{
age: 8,
extensions: ['ts', 'tsx']
}如果要將此代碼遷移到 TypeScript 以使其類(lèi)型安全,則必須使用泛型。 我們可以通過(guò)添加以下突出顯示的行來(lái)重構(gòu)代碼:
function pickObjectKeys(obj: T, keys: K[]) {
let result = {} as Pick
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'])
然后將 obj 函數(shù)參數(shù)設(shè)置為 T 表示的任何類(lèi)型,并將key設(shè)置為數(shù)組, 無(wú)論 K 代表什么類(lèi)型。
由于在語(yǔ)言對(duì)象的情況下 T 將 age 設(shè)置為數(shù)字并將 extensions 設(shè)置為字符串?dāng)?shù)組,因此,變量 ageAndExtensions 現(xiàn)在將被分配具有屬性 age: number 和 extensions: string[] 的對(duì)象的類(lèi)型。
這會(huì)根據(jù)提供給 pickObjectKeys 的參數(shù)強(qiáng)制執(zhí)行返回類(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ù)創(chuàng)建建議。 這顯示在以下屏幕截圖中:
了解如何在 TypeScript 中創(chuàng)建泛型后,您現(xiàn)在可以繼續(xù)探索在特定情況下使用泛型。 本教程將首先介紹如何在函數(shù)中使用泛型。
將泛型與函數(shù)一起使用的最常見(jiàn)場(chǎng)景之一是當(dāng)您有一些代碼不容易為所有用例鍵入時(shí)。 為了使該功能適用于更多情況,您可以包括泛型類(lèi)型。
在此步驟中,您將運(yùn)行一個(gè)恒等函數(shù)示例來(lái)說(shuō)明這一點(diǎn)。 您還將探索一個(gè)異步示例,了解何時(shí)將類(lèi)型參數(shù)直接傳遞給您的泛型,以及如何為您的泛型類(lèi)型參數(shù)創(chuàng)建約束和默認(rèn)值。
看一下下面的函數(shù),它返回作為第一個(gè)參數(shù)傳入的內(nèi)容:
function identity(value) {
return value;
}您可以添加以下代碼以使函數(shù)在 TypeScript 中類(lèi)型安全:
function identity(value: T): T{
return value;
}
你把你的函數(shù)變成了一個(gè)泛型函數(shù),它接受泛型類(lèi)型參數(shù) T,這是第一個(gè)參數(shù)的類(lèi)型,然后將返回類(lèi)型設(shè)置為與 : T 相同。
接下來(lái),添加以下代碼來(lái)試用該功能:
function identity(value: T): T {
return value;
}
const result = identity(123);
結(jié)果的類(lèi)型為 123,這是您傳入的確切數(shù)字。這里的 TypeScript 從調(diào)用代碼本身推斷泛型類(lèi)型。 這樣調(diào)用代碼不需要傳遞任何類(lèi)型參數(shù)。 您也可以顯式地將泛型類(lèi)型參數(shù)設(shè)置為您想要的類(lèi)型:
function identity(value: T): T {
return value;
}
const result = identity(123);
在此代碼中,result 具有類(lèi)型編號(hào)。 通過(guò)使用
直接傳遞類(lèi)型參數(shù)在使用自定義類(lèi)型時(shí)也很有用。 例如,看看下面的代碼:
type ProgrammingLanguage = {
name: string;
};
function identity(value: T): T {
return value;
}
const result = identity({ name: "TypeScript" }); 在此代碼中,result 具有自定義類(lèi)型 ProgrammingLanguage,因?yàn)樗苯觽鬟f給標(biāo)識(shí)函數(shù)。 如果您沒(méi)有明確包含類(lèi)型參數(shù),則結(jié)果將具有類(lèi)型 { name: string } 。
使用 JavaScript 時(shí)的另一個(gè)常見(jiàn)示例是使用包裝函數(shù)從 API 檢索數(shù)據(jù):
async function fetchApi(path: string) {
const response = await fetch(`https://example.com/api${path}`)
return response.json();
}此異步函數(shù)將 URL 路徑作為參數(shù),使用 fetch API 向 URL 發(fā)出請(qǐng)求,然后返回 JSON 響應(yīng)值。 在這種情況下,fetchApi 函數(shù)的返回類(lèi)型將是 Promise
將 any 作為返回類(lèi)型并不是很有幫助。 any 表示任何 JavaScript 值,使用它你將失去靜態(tài)類(lèi)型檢查,這是 TypeScript 的主要優(yōu)點(diǎn)之一。 如果您知道 API 將返回給定形狀的對(duì)象,則可以使用泛型使此函數(shù)類(lèi)型安全:
async function fetchApi(path: string): Promise {
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
注意:由于您的函數(shù)是異步的,因此,您必須返回一個(gè) Promise 對(duì)象。 TypeScript Promise 類(lèi)型本身是一種通用類(lèi)型,它接受 promise 解析為的值的類(lèi)型。
如果仔細(xì)查看您的函數(shù),您會(huì)發(fā)現(xiàn)參數(shù)列表或 TypeScript 能夠推斷其值的任何其他地方都沒(méi)有使用泛型。 這意味著調(diào)用代碼在調(diào)用您的函數(shù)時(shí)必須顯式傳遞此泛型的類(lèi)型。
以下是檢索用戶(hù)數(shù)據(jù)的 fetchApi 通用函數(shù)的可能實(shí)現(xiàn):
type User = {
name: string;
}
async function fetchApi(path: string): Promise {
const response = await fetch(`https://example.com/api${path}`);
return response.json();
}
const data = await fetchApi('/users')
export {} 在此代碼中,您將創(chuàng)建一個(gè)名為 User 的新類(lèi)型,并使用該類(lèi)型的數(shù)組 (User[]) 作為 ResultType 泛型參數(shù)的類(lèi)型。 數(shù)據(jù)變量現(xiàn)在具有類(lèi)型 User[] 而不是任何。
注意:當(dāng)您使用 await 異步處理函數(shù)的結(jié)果時(shí),返回類(lèi)型將是 Promise
像您一樣創(chuàng)建通用的 fetchApi 函數(shù),調(diào)用代碼始終必須提供類(lèi)型參數(shù)。 如果調(diào)用代碼不包含泛型類(lèi)型,則 ResultType 將綁定為未知。 以下面的實(shí)現(xiàn)為例:
async function fetchApi(path: string): Promise {
const response = await fetch(`https://example.com/api${path}`);
return
response.json();
}
const data = await fetchApi('/users')
console.log(data.a)
export {}
此代碼嘗試訪問(wèn)數(shù)據(jù)的理論上的屬性。 但由于數(shù)據(jù)類(lèi)型未知,這段代碼將無(wú)法訪問(wèn)對(duì)象的屬性。
如果您不打算將特定類(lèi)型添加到泛型函數(shù)的每次調(diào)用中,則可以將默認(rèn)類(lèi)型添加到泛型類(lèi)型參數(shù)中。 這可以通過(guò)在泛型類(lèi)型之后添加 = DefaultType 來(lái)完成,如下所示:
async function fetchApi>(path: string): Promise {
const response = await fetch(`https://example.com/api${path}`);
return response.json();
}
const data = await fetchApi('/users')
console.log(data.a)
export {}
使用此代碼,您不再需要在調(diào)用 fetchApi 函數(shù)時(shí)將類(lèi)型傳遞給 ResultType 泛型參數(shù),因?yàn)樗哂心J(rèn)類(lèi)型 Record
在某些情況下,泛型類(lèi)型參數(shù)需要只允許將某些形狀傳遞給泛型。 要為您的泛型創(chuàng)建額外的特殊層,您可以對(duì)您的參數(shù)施加約束。
假設(shè)您有一個(gè)存儲(chǔ)限制,您只能存儲(chǔ)所有屬性都具有字符串值的對(duì)象。 為此,您可以創(chuàng)建一個(gè)函數(shù),它接受任何對(duì)象并返回另一個(gè)對(duì)象,該對(duì)象具有與原始對(duì)象相同的鍵,但所有值都轉(zhuǎn)換為字符串。 這個(gè)函數(shù)將被稱(chēng)為 stringifyObjectKeyValues。
這個(gè)函數(shù)將是一個(gè)通用函數(shù)。 這樣,您就可以使生成的對(duì)象具有與原始對(duì)象相同的形狀。 該函數(shù)將如下所示:
function stringifyObjectKeyValues>(obj: T) {
return Object.keys(obj).reduce((acc, key) => ({
...acc,
[key]: JSON.stringify(obj[key])
}), {} as { [K in keyof T]: string })
}
在此代碼中,stringifyObjectKeyValues 使用 reduce 數(shù)組方法迭代原始鍵數(shù)組,將值字符串化并將它們添加到新數(shù)組中。
為確保調(diào)用代碼始終將對(duì)象傳遞給您的函數(shù),您在泛型類(lèi)型 T 上使用類(lèi)型約束,如以下突出顯示的代碼所示:
function stringifyObjectKeyValues>(obj: T) {
// ...
}
extends Record
在這種情況下,Record
在調(diào)用 reduce 時(shí),reducer 函數(shù)的返回類(lèi)型基于累加器的初始值。 {} as { [K in keyof T]: string } 代碼通過(guò)對(duì)空對(duì)象 {} 進(jìn)行類(lèi)型轉(zhuǎn)換,將累加器初始值的類(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)型,本教程將在后面的部分中進(jìn)一步探討。
以下代碼顯示了 stringifyObjectKeyValues 函數(shù)的實(shí)現(xiàn):
function stringifyObjectKeyValues>(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;
}這將確保返回值與函數(shù)的目的一致。
本節(jié)介紹了將泛型與函數(shù)一起使用的多種方法,包括直接分配類(lèi)型參數(shù)以及為參數(shù)形狀設(shè)置默認(rèn)值和約束。
接下來(lái),您將通過(guò)一些示例來(lái)了解泛型如何使接口和類(lèi)適用于更多情況。
在 TypeScript 中創(chuàng)建接口和類(lèi)時(shí),使用泛型類(lèi)型參數(shù)來(lái)設(shè)置結(jié)果對(duì)象的形狀會(huì)很有用。
例如,一個(gè)類(lèi)可能具有不同類(lèi)型的屬性,具體取決于傳遞給構(gòu)造函數(shù)的內(nèi)容。 在本節(jié)中,您將了解在類(lèi)和接口中聲明泛型類(lèi)型參數(shù)的語(yǔ)法,并檢查 HTTP 應(yīng)用程序中的常見(jiàn)用例。
要?jiǎng)?chuàng)建通用接口,您可以在接口名稱(chēng)之后添加類(lèi)型參數(shù)列表:
interface MyInterface{
field: T
}
這聲明了一個(gè)接口,該接口具有一個(gè)屬性字段,其類(lèi)型由傳遞給 T 的類(lèi)型確定。
對(duì)于類(lèi),語(yǔ)法幾乎相同:
class MyClass{
field: T
constructor(field: T) {
this.field = field
}
}
通用接口/類(lèi)的一個(gè)常見(jiàn)用例是當(dāng)您有一個(gè)字段,其類(lèi)型取決于客戶(hù)端代碼如何使用接口/類(lèi)時(shí)。
假設(shè)您有一個(gè) HttpApplication 類(lèi),用于處理對(duì) API 的 HTTP 請(qǐng)求,并且某些上下文值將傳遞給每個(gè)請(qǐng)求處理程序。 這樣做的一種方法是:
class HttpApplication{
context: Context
constructor(context: Context) {
this.context = context;
}
// ... implementation
get(url: string, handler: (context: Context) => Promise): this {
// ... implementation
return this;
}
}
此類(lèi)存儲(chǔ)一個(gè)上下文,其類(lèi)型作為 get 方法中處理函數(shù)的參數(shù)類(lèi)型傳入。 在使用過(guò)程中,傳遞給 get 處理程序的參數(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)
});
在此實(shí)現(xiàn)中,TypeScript 會(huì)將 context.someValue 的類(lèi)型推斷為布爾值。
現(xiàn)在已經(jīng)了解了類(lèi)和接口中泛型的一些示例,您現(xiàn)在可以繼續(xù)創(chuàng)建泛型自定義類(lèi)型。 將泛型應(yīng)用于類(lèi)型的語(yǔ)法類(lèi)似于將泛型應(yīng)用于接口和類(lèi)的語(yǔ)法。 看看下面的代碼:
type MyIdentityType= T
此泛型類(lèi)型返回作為類(lèi)型參數(shù)傳遞的類(lèi)型。 假設(shè)您使用以下代碼實(shí)現(xiàn)了這種類(lèi)型:
...type B = MyIdentityType
在這種情況下,類(lèi)型 B 將是類(lèi)型 number。
通用類(lèi)型通常用于創(chuàng)建輔助類(lèi)型,尤其是在使用映射類(lèi)型時(shí)。 TypeScript 提供了許多預(yù)構(gòu)建的幫助程序類(lèi)型。
一個(gè)這樣的例子是 Partial 類(lèi)型,它采用類(lèi)型 T 并返回另一個(gè)與 T 具有相同形狀的類(lèi)型,但它們的所有字段都設(shè)置為可選。 Partial 的實(shí)現(xiàn)如下所示:
type Partial= {
[P in keyof T]?: T[P];
};
這里的 Partial 類(lèi)型接受一個(gè)類(lèi)型,遍歷其屬性類(lèi)型,然后將它們作為可選類(lèi)型返回到新類(lèi)型中。
注意:由于 Partial 已經(jīng)內(nèi)置到 TypeScript 中,因此將此代碼編譯到您的 TypeScript 環(huán)境中會(huì)重新聲明 Partial 并引發(fā)錯(cuò)誤。 這里引用的Partial的實(shí)現(xiàn)只是為了說(shuō)明。
要了解泛型類(lèi)型有多么強(qiáng)大,假設(shè)您有一個(gè)對(duì)象字面量,用于存儲(chǔ)從一家商店到您的業(yè)務(wù)分銷(xiāo)網(wǎng)絡(luò)中所有其他商店的運(yùn)輸成本。 每個(gè)商店將由一個(gè)三字符代碼標(biāo)識(shí),如下所示:
{
ABC: {
ABC: null,
DEF: 12,
GHI: 13,
},
DEF: {
ABC: 12,
DEF: null,
GHI: 17,
},
GHI: {
ABC: 13,
DEF: 17,
GHI: null,
},
}該對(duì)象是表示商店位置的對(duì)象的集合。 在每個(gè)商店位置中,都有表示運(yùn)送到其他商店的成本的屬性。 例如,從 ABC 運(yùn)往 DEF 的成本是 12。從一家商店到它自己的運(yùn)費(fèi)為空,因?yàn)楦緵](méi)有運(yùn)費(fèi)。
為確保其他商店的位置具有一致的值,并且商店運(yùn)送到自身的始終為空,您可以創(chuàng)建一個(gè)通用的幫助器類(lèi)型:
type IfSameKeyThanParentTOtherwiseOtherType= {
[K in Keys]: {
[SameThanK in K]: T;
} &
{ [OtherThanK in Exclude]: OtherType };
};
IfSameKeyThanParentTOtherwiseOtherType 類(lèi)型接收三個(gè)通用類(lèi)型。 第一個(gè),Keys,是你想要確保你的對(duì)象擁有的所有鍵。 在這種情況下,它是所有商店代碼的聯(lián)合。
T 是當(dāng)嵌套對(duì)象字段具有與父對(duì)象上的鍵相同的鍵時(shí)的類(lèi)型,在這種情況下,它表示運(yùn)送到自身的商店位置。 最后,OtherType 是 key 不同時(shí)的類(lèi)型,表示一個(gè)商店發(fā)貨到另一個(gè)商店。
你可以像這樣使用它:
...
type Code = 'ABC' | 'DEF' | 'GHI'
const shippingCosts: IfSameKeyThanParentTOtherwiseOtherType= {
ABC: {
ABC: null,
DEF: 12,
GHI: 13,
},
DEF: {
ABC: 12,
DEF: null,
GHI: 17,
},
GHI: {
ABC: 13,
DEF: 17,
GHI: null,
},
}
此代碼現(xiàn)在強(qiáng)制執(zhí)行類(lèi)型形狀。 如果您將任何鍵設(shè)置為無(wú)效值,TypeScript 將報(bào)錯(cuò):
...
const shippingCosts: IfSameKeyThanParentTOtherwiseOtherType= {
ABC: {
ABC: 12,
DEF: 12,
GHI: 13,
},
DEF: {
ABC: 12,
DEF: null,
GHI: 17,
},
GHI: {
ABC: 13,
DEF: 17,
GHI: null,
},
}
由于 ABC 與自身之間的運(yùn)費(fèi)不再為空,TypeScript 將拋出以下錯(cuò)誤:
OutputType 'number' is not assignable to type 'null'.(2322)
您現(xiàn)在已經(jīng)嘗試在接口、類(lèi)和自定義幫助程序類(lèi)型中使用泛型。 接下來(lái),您將進(jìn)一步探討本教程中已經(jīng)多次出現(xiàn)的主題:使用泛型創(chuàng)建映射類(lèi)型。
在使用 TypeScript 時(shí),有時(shí)您需要?jiǎng)?chuàng)建一個(gè)與另一種類(lèi)型具有相同形狀的類(lèi)型。 這意味著它應(yīng)該具有相同的屬性,但屬性的類(lèi)型設(shè)置為不同的東西。 對(duì)于這種情況,使用映射類(lèi)型可以重用初始類(lèi)型形狀并減少應(yīng)用程序中的重復(fù)代碼。
在 TypeScript 中,這種結(jié)構(gòu)被稱(chēng)為映射類(lèi)型并依賴(lài)于泛型。 在本節(jié)中,您將看到如何創(chuàng)建映射類(lèi)型。
想象一下,您想要?jiǎng)?chuàng)建一個(gè)類(lèi)型,給定另一個(gè)類(lèi)型,該類(lèi)型返回一個(gè)新類(lèi)型,其中所有屬性都設(shè)置為具有布爾值。 您可以使用以下代碼創(chuàng)建此類(lèi)型:
type BooleanFields= {
[K in keyof T]: boolean;
}
在這種類(lèi)型中,您使用語(yǔ)法 [K in keyof T] 來(lái)指定新類(lèi)型將具有的屬性。 keyof T 運(yùn)算符用于返回具有 T 中所有可用屬性名稱(chēng)的聯(lián)合。然后使用 K in 語(yǔ)法指定新類(lèi)型的屬性是返回的聯(lián)合類(lèi)型中當(dāng)前可用的所有屬性 T鍵。
這將創(chuàng)建一個(gè)名為 K 的新類(lèi)型,它綁定到當(dāng)前屬性的名稱(chēng)。 這可用于使用語(yǔ)法 T[K] 訪問(wèn)原始類(lèi)型中此屬性的類(lèi)型。 在這種情況下,您將屬性的類(lèi)型設(shè)置為布爾值。
此 BooleanFields 類(lèi)型的一個(gè)使用場(chǎng)景是創(chuàng)建一個(gè)選項(xiàng)對(duì)象。 假設(shè)您有一個(gè)數(shù)據(jù)庫(kù)模型,例如用戶(hù)。
從數(shù)據(jù)庫(kù)中獲取此模型的記錄時(shí),您還將允許傳遞一個(gè)指定要返回哪些字段的對(duì)象。
該對(duì)象將具有與模型相同的屬性,但類(lèi)型設(shè)置為布爾值。 在一個(gè)字段中傳遞 true 意味著您希望它被返回,而 false 則意味著您希望它被省略。
您可以在現(xiàn)有模型類(lèi)型上使用 BooleanFields 泛型來(lái)返回與模型具有相同形狀的新類(lèi)型,但所有字段都設(shè)置為布爾類(lèi)型,如以下突出顯示的代碼所示:
type BooleanFields= {
[K in keyof T]: boolean;
};
type User = {
email: string;
name: string;
}
type UserFetchOptions = BooleanFields;
在此示例中,UserFetchOptions 將與這樣創(chuàng)建它相同:
type UserFetchOptions = {
email: boolean;
name: boolean;
}
創(chuàng)建映射類(lèi)型時(shí),您還可以為字段提供修飾符。 一個(gè)這樣的例子是 TypeScript 中可用的現(xiàn)有泛型類(lèi)型,稱(chēng)為 Readonly
type Readonly= {
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ō)明的目的。
請(qǐng)注意修飾符 readonly,它作為前綴添加到此代碼中的 [K in keyof T] 部分。
目前,可以在映射類(lèi)型中使用的兩個(gè)可用修飾符是 readonly 修飾符,它必須作為前綴添加到屬性,以及 ? 修飾符,可以作為屬性的后綴添加。 這 ? 修飾符將字段標(biāo)記為可選。
兩個(gè)修飾符都可以接收一個(gè)特殊的前綴來(lái)指定是否應(yīng)該刪除修飾符 (-) 或添加 (+)。 如果僅提供修飾符,則假定為 +。
現(xiàn)在您可以使用映射類(lèi)型基于您已經(jīng)創(chuàng)建的類(lèi)型形狀創(chuàng)建新類(lèi)型,您可以繼續(xù)討論泛型的最終用例:條件類(lèi)型。
在本節(jié)中,您將嘗試 TypeScript 中泛型的另一個(gè)有用功能:創(chuàng)建條件類(lèi)型。 首先,您將了解條件類(lèi)型的基本結(jié)構(gòu)。 然后,您將通過(guò)創(chuàng)建一個(gè)條件類(lèi)型來(lái)探索高級(jí)用例,該條件類(lèi)型省略基于點(diǎn)表示法的對(duì)象類(lèi)型的嵌套字段。
條件類(lèi)型是根據(jù)某些條件具有不同結(jié)果類(lèi)型的泛型類(lèi)型。 例如,看看下面的泛型類(lèi)型 IsStringType
type IsStringType= T extends string ? true : false;
在此代碼中,您正在創(chuàng)建一個(gè)名為 IsStringType 的新泛型類(lèi)型,它接收單個(gè)類(lèi)型參數(shù) T。在您的類(lèi)型定義中,您使用的語(yǔ)法看起來(lái)像使用 JavaScript 中的三元運(yùn)算符的條件表達(dá)式:T extends string ? 真假。
此條件表達(dá)式正在檢查類(lèi)型 T 是否擴(kuò)展了類(lèi)型字符串。 如果是,則結(jié)果類(lèi)型將是完全正確的類(lèi)型; 否則,它將被設(shè)置為 false 類(lèi)型。
注意:此條件表達(dá)式是在編譯期間求值的。 TypeScript 僅適用于類(lèi)型,因此請(qǐng)確保始終將類(lèi)型聲明中的標(biāo)識(shí)符讀取為類(lèi)型,而不是值。 在此代碼中,您使用每個(gè)布爾值的確切類(lèi)型,true 和 false。
要嘗試這種條件類(lèi)型,請(qǐng)將一些類(lèi)型作為其類(lèi)型參數(shù)傳遞:
type IsStringType= T extends string ? true : false;
type A = "abc";
type B = {
name: string;
};
type ResultA = IsStringType;
type ResultB = IsStringType;
在此代碼中,您創(chuàng)建了兩種類(lèi)型,A 和 B。類(lèi)型 A 是字符串文字“abc”的類(lèi)型,而類(lèi)型 B 是具有名為 name of type string 屬性的對(duì)象的類(lèi)型。
然后將這兩種類(lèi)型與 IsStringType 條件類(lèi)型一起使用,并將結(jié)果類(lèi)型存儲(chǔ)到兩個(gè)新類(lèi)型 ResultA 和 ResultB 中。
如果檢查 ResultA 和 ResultB 的結(jié)果類(lèi)型,您會(huì)注意到 ResultA 類(lèi)型設(shè)置為準(zhǔn)確的類(lèi)型 true,而 ResultB 類(lèi)型設(shè)置為 false。 這是正確的,因?yàn)?A 確實(shí)擴(kuò)展了字符串類(lèi)型而 B 沒(méi)有擴(kuò)展字符串類(lèi)型,因?yàn)樗辉O(shè)置為具有字符串類(lèi)型的單個(gè)名稱(chēng)屬性的對(duì)象的類(lèi)型。
條件類(lèi)型的一個(gè)有用特性是它允許您使用特殊關(guān)鍵字 infer 在 extends 子句中推斷類(lèi)型信息。 然后可以在條件的真實(shí)分支中使用這種新類(lèi)型。 此功能的一種可能用法是檢索任何函數(shù)類(lèi)型的返回類(lèi)型。
編寫(xiě)以下 GetReturnType 類(lèi)型來(lái)說(shuō)明這一點(diǎn):
type GetReturnType= 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ù)簽名匹配的類(lèi)型,該函數(shù)簽名接受可變數(shù)量的參數(shù)(包括零),然后您推斷返回 該函數(shù)的類(lèi)型創(chuàng)建一個(gè)新類(lèi)型 U,可在條件的真實(shí)分支內(nèi)使用。
U 的類(lèi)型將綁定到傳遞函數(shù)的返回值的類(lèi)型。 如果傳遞的類(lèi)型 T 不是函數(shù),則代碼將返回 never 類(lèi)型。
使用您的類(lèi)型和以下代碼:
type GetReturnType= T extends (...args: any[]) => infer U ? U : never;
function someFunction() {
return true;
}
type ReturnTypeOfSomeFunction = GetReturnType;
在此代碼中,您將創(chuàng)建一個(gè)名為 someFunction 的函數(shù),該函數(shù)返回 true。 然后使用 typeof 運(yùn)算符將此函數(shù)的類(lèi)型傳遞給 GetReturnType 泛型,并將結(jié)果類(lèi)型存儲(chǔ)在 ReturnTypeOfSomeFunction 類(lèi)型中。
由于 someFunction 變量的類(lèi)型是函數(shù),因此條件類(lèi)型將評(píng)估條件的真實(shí)分支。 這將返回類(lèi)型 U 作為結(jié)果。
類(lèi)型 U 是從函數(shù)的返回類(lèi)型推斷出來(lái)的,在本例中是布爾值。 如果檢查 ReturnTypeOfSomeFunction 的類(lèi)型,您會(huì)發(fā)現(xiàn)它已正確設(shè)置為布爾類(lèi)型。
條件類(lèi)型是 TypeScript 中可用的最靈活的功能之一,允許創(chuàng)建一些高級(jí)實(shí)用程序類(lèi)型。
在本節(jié)中,您將通過(guò)創(chuàng)建一個(gè)名為 NestedOmit
此實(shí)用程序類(lèi)型將能夠省略對(duì)象中的字段,就像現(xiàn)有的 Omit
使用新的 NestedOmit
type SomeType = {
a: {
b: string,
c: {
d: number;
e: string[]
},
f: number
}
g: number | string,
h: {
i: string,
j: number,
},
k: {
l: number,
}
}
type Result = NestedOmit;
此代碼聲明了一個(gè)名為 SomeType 的類(lèi)型,它具有嵌套屬性的多級(jí)結(jié)構(gòu)。 使用 NestedOmit 泛型,傳入類(lèi)型,然后列出要省略的屬性的鍵。
請(qǐng)注意如何在第二個(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)型。
要嘗試這個(gè)泛型,首先創(chuàng)建一個(gè)名為 NestedOmit 的泛型類(lèi)型,它接受兩個(gè)類(lèi)型參數(shù):
type NestedOmit, KeysToOmit extends string>
第一個(gè)類(lèi)型參數(shù)稱(chēng)為 T,它必須是可分配給 Record
第二個(gè)類(lèi)型參數(shù)叫做KeysToOmit,必須是字符串類(lèi)型。 您將使用它來(lái)指定要從類(lèi)型 T 中省略的鍵。
接下來(lái),通過(guò)添加以下突出顯示的代碼來(lái)檢查 KeysToOmit 是否可分配給 ${infer KeyPart1}.${infer KeyPart2} 類(lèi)型:
type NestedOmit, KeysToOmit extends string>=
KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}`
在這里,您使用模板文字字符串類(lèi)型,同時(shí),利用條件類(lèi)型推斷模板文字本身內(nèi)部的其他兩種類(lèi)型。
通過(guò)推斷模板文字字符串類(lèi)型的兩個(gè)部分,您將字符串拆分為另外兩個(gè)字符串。 第一部分將分配給 KeyPart1 類(lèi)型,并將包含第一個(gè)點(diǎn)之前的所有內(nèi)容。
第二部分將分配給 KeyPart2 類(lèi)型,并將包含第一個(gè)點(diǎn)之后的所有內(nèi)容。 如果您將“a.b.c”作為 KeysToOmit 傳遞,則最初 KeyPart1 將設(shè)置為確切的字符串類(lèi)型“a”,而 KeyPart2 將設(shè)置為“b.c”。
接下來(lái),您將添加三元運(yùn)算符來(lái)定義條件的第一個(gè)真分支:
type NestedOmit, KeysToOmit extends string> =
KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}`
?
KeyPart1 extends keyof T
這使用 KeyPart1 extends keyof T 來(lái)檢查 KeyPart1 是否是給定類(lèi)型 T 的有效屬性。如果您確實(shí)有一個(gè)有效的鍵,請(qǐng)?zhí)砑右韵麓a以使條件計(jì)算為兩種類(lèi)型之間的交集:
type NestedOmit, KeysToOmit extends string> =
KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}`
?
KeyPart1 extends keyof T
?
Omit
& {
[NewKeys in KeyPart1]: NestedOmit
}
Omit
您正在使用 Omit 刪除 T[KeyPart1] 中的一些嵌套字段,為此,您必須重建 T[KeyPart1] 的類(lèi)型。
為避免重建整個(gè) T 類(lèi)型,您使用 Omit 僅從 T 中刪除 KeyPart1,同時(shí)保留其他字段。 然后,您將在下一部分的類(lèi)型中重建 T[KeyPart1]。
[KeyPart1 中的新鍵]:NestedOmit
這是您要?jiǎng)h除的字段的父項(xiàng)。 如果您通過(guò)了 a.b.c,在第一次評(píng)估您的條件時(shí),它將是“a”中的 NewKeys。
然后將此屬性的類(lèi)型設(shè)置為遞歸調(diào)用 NestedOmit 實(shí)用程序類(lèi)型的結(jié)果,但現(xiàn)在使用 T[NewKeys] 將此屬性的類(lèi)型作為第一個(gè)類(lèi)型參數(shù)傳遞給 T,并作為第二個(gè)類(lèi)型參數(shù)傳遞其余鍵以點(diǎn)表示法表示,在 KeyPart2 中可用。
在內(nèi)部條件的 false 分支中,返回綁定到 T 的當(dāng)前類(lèi)型,就好像 KeyPart1 不是 T 的有效鍵一樣:
type NestedOmit, KeysToOmit extends string> =
KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}`
?
KeyPart1 extends keyof T
?
Omit
& {
[NewKeys in KeyPart1]: NestedOmit
}
: T
條件的這個(gè)分支意味著你試圖省略一個(gè) T 中不存在的字段。在這種情況下,沒(méi)有必要再進(jìn)一步了。
最后,在外部條件的 false 分支中,使用現(xiàn)有的 Omit 實(shí)用程序類(lèi)型從 Type 中省略 KeysToOmit:
type NestedOmit, KeysToOmit extends string> =
KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}`
?
KeyPart1 extends keyof T
?
Omit
& {
[NewKeys in KeyPart1]: NestedOmit
}
: T
: Omit;
如果條件 KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}` 為假,這意味著 KeysToOmit 沒(méi)有使用點(diǎn)符號(hào),因此,您可以使用現(xiàn)有的 Omit 實(shí)用程序類(lèi)型。
現(xiàn)在,要使用新的 NestedOmit 條件類(lèi)型,請(qǐng)創(chuàng)建一個(gè)名為 NestedObject 的新類(lèi)型:
type NestedObject = {
a: {
b: {
c: number;
d: number;
};
e: number;
};
f: number;
};然后對(duì)其調(diào)用 NestedOmit 以省略 a.b.c 處可用的嵌套字段:
type Result = NestedOmit;
在第一次評(píng)估條件類(lèi)型時(shí),外部條件將為真,因?yàn)樽址淖诸?lèi)型“a.b.c”可分配給模板文字類(lèi)型“${infer KeyPart1}.${infer KeyPart2}”。
在這種情況下,KeyPart1 將被推斷為字符串文字類(lèi)型“a”,而 KeyPart2 將被推斷為字符串的剩余部分,在本例中為“b.c”。
現(xiàn)在將評(píng)估內(nèi)部條件。 這將評(píng)估為真,因?yàn)榇藭r(shí) KeyPart1 是 T 的鍵。KeyPart1 現(xiàn)在是“a”,而 T 確實(shí)有一個(gè)屬性“a”:
type NestedObject = {
a: {
b: {
c: number;
d: number;
};
e: number;
};
f: number;
};繼續(xù)評(píng)估條件,您現(xiàn)在位于內(nèi)部 true 分支內(nèi)。 這將構(gòu)建一個(gè)新
新聞標(biāo)題:如何在 TypeScript 中使用泛型
文章起源:http://uogjgqi.cn/article/djgeehd.html

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢(xún)/運(yùn)營(yíng)咨詢(xún)/技術(shù)建議/互聯(lián)網(wǎng)交流