新聞中心
介紹
泛型是靜態(tài)類型語言的基本特征,允許開發(fā)人員將類型作為參數(shù)傳遞給另一種類型、函數(shù)或其他結(jié)構(gòu)。當(dāng)開發(fā)人員使他們的組件成為通用組件時,他們使該組件能夠接受和強(qiáng)制在使用組件時傳入的類型,這提高了代碼靈活性,使組件可重用并消除重復(fù)。

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


咨詢
建站咨詢
