亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

泛型是靜態(tài)類型語(yǔ)言的基本特征,允許將類型作為參數(shù)傳遞給另一個(gè)類型、函數(shù)、或者其他結(jié)構(gòu)。TypeScript 支持泛型作為將類型安全引入組件的一種方式。這些組件接受參數(shù)和返回值,其類型將是不確定的,直到它在代碼中被使用。下面將通過一些示例,探索如何在函數(shù)、類型、類和接口中使用泛型,以及使用泛型創(chuàng)建映射類型和條件類型。

1. 泛型語(yǔ)法

首先來看看TypeScript 泛型的語(yǔ)法。泛型的語(yǔ)法為 <T>,其中 T 表示傳入的類型。在這種情況下,T 和函數(shù)參數(shù)的工作方式相同,其作為將在創(chuàng)建結(jié)構(gòu)實(shí)例時(shí)聲明的類型的占位符。因此,尖括號(hào)內(nèi)指定的泛型類型也稱為泛型類型參數(shù)。泛型的定義可以有多個(gè)泛型類型采參數(shù),例如:<T, K, Z>。

注意:通常使用單個(gè)字母來命名泛型類型。這不是語(yǔ)法規(guī)則,我們也可以像 TypeScript 中的任何其他類型一樣命名泛型,但這種約定有助于向閱讀代碼的人傳達(dá)泛型類型不需要特定類型。

下面通過一個(gè)函數(shù)的例子來看看泛型的基本語(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ì)象。下面來測(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 以使其類型安全,則可以使用泛型。重構(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ù)類型,其中 K 被分配給了一個(gè)類型,該類型是 T 中的 key 的集合。然后將 obj 參數(shù)設(shè)置為 T,表示任何類型,并將 keys 設(shè)置為數(shù)組,無論 K 是什么類型。

當(dāng)傳入的 obj 參數(shù)為language 對(duì)象時(shí),T將 age 設(shè)置為number類型,將 extensions 設(shè)置為string[]類型,所以變量 ageAndExtensions  的類型為:

{
  age: number;
  extensions: string[];
}

這樣就會(huì)根據(jù)提供給 pickObjectKeys 的參數(shù)來判斷返回值的類型,從而允許函數(shù)在知道需要強(qiáng)制執(zhí)行的特定類型之前靈活地強(qiáng)制執(zhí)行類型結(jié)構(gòu)。當(dāng)在 Visual Studio Code 等 IDE 中使用該函數(shù)時(shí),這使得開發(fā)體驗(yàn)更好,它將根據(jù)提供的對(duì)象為 keys 參數(shù)提供建議:

一文讀懂 TypeScript 泛型及應(yīng)用圖片

2. 在函數(shù)中使用泛型

將泛型與函數(shù)一起使用的最常見場(chǎng)景之一就是,當(dāng)有一些不容易為所有的用例定義類型時(shí),為了使該函數(shù)適用于更多情況,就可以使用泛型來定義。下面來看看在函數(shù)中使用泛型的常見場(chǎng)景。

(1)分配泛型參數(shù)

先來看下面的函數(shù),它返回函數(shù)參數(shù)傳入的內(nèi)容:

function identity(value) {
  return value;
}

可以為其添加泛型類型以使函數(shù)的類型更安全:

function identity<T>(value: T): T {
  return value;
}

這里將函數(shù)轉(zhuǎn)化為接受泛型類型參數(shù) T 的泛型函數(shù),它第一個(gè)參數(shù)的類型,然后將返回類型也設(shè)置為 T 。下面來測(cè)試一下這個(gè)函數(shù):

function identity<T>(value: T): T {
  return value;
}

const result = identity(123);

result 的類型為 123,這是我們傳入的數(shù)字:

一文讀懂 TypeScript 泛型及應(yīng)用圖片

此時(shí),TypeScript 使用調(diào)用代碼本身來推斷泛型類型。這樣調(diào)用代碼不需要傳遞任何類型參數(shù)。當(dāng)然,我們也可以顯式地將泛型類型參數(shù)設(shè)置為想要的類型:

function identity<T>(value: T): T {
  return value;
}

const result = identity<number>(123);

在這段代碼中,result的類型就是 number:

一文讀懂 TypeScript 泛型及應(yīng)用圖片

這里使用 <number> 定義了傳入類型,讓 TypeScript 標(biāo)識(shí)函數(shù)的泛型類型參數(shù) T 為 number 類型。這將強(qiáng)制number類型作為參數(shù)和返回值的類型。當(dāng)再傳入其他類型時(shí),就會(huì)報(bào)錯(cuò):

一文讀懂 TypeScript 泛型及應(yīng)用圖片

(2)直接傳遞類型參數(shù)

在使用自定義類型時(shí),直接傳遞類型參數(shù)也很有用。來看下面的代碼:

type ProgrammingLanguage = {
  name: string;
};

function identity<T>(value: T): T {
  return value;
}

const result = identity<ProgrammingLanguage>({ name: "TypeScript" });

在這段代碼中,result 為自定義類型 ProgrammingLanguage,它直接傳遞給了 identity 函數(shù)。如果沒有顯式地定義類型參數(shù),則result的類型就是 { name: string } 。

另一個(gè)常見的例子就是使用函數(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ù)的返回類型是 Promise<any>,這是 fetch 的響應(yīng)對(duì)象的 json() 調(diào)用的返回類型。

將 any 作為返回類型并不會(huì)有任何作用,它表示任意類型,使用它將失去靜態(tài)類型檢查。如果我們知道 API 將返回指定結(jié)構(gòu)的對(duì)象,則可以使用泛型以使此函數(shù)類型更安全:

async function fetchApi<ResultType>(path: string): Promise<ResultType> {
  const response = await fetch(`https://example.com/api${path}`);
  return response.json();
}

這里就將函數(shù)轉(zhuǎn)換為接受 ResultType 泛型類型參數(shù)的泛型函數(shù)。此泛型類型用于函數(shù)的返回類型:Promise<ResultType>。

注意:由于這個(gè)函數(shù)是異步的,因此會(huì)返回一個(gè) Promise 對(duì)象。TypeScript 中的 Promise 類型本身是一個(gè)泛型類型,它接受 Promise 解析為的值的類型。

可以看到,泛型并沒有在參數(shù)列表中使用,也沒有在TypeScript能夠推斷其值的其他地方使用。這意味著在調(diào)用函數(shù)時(shí),必須顯式地傳遞此泛型的類型:

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 的新類型,并使用該類型的數(shù)組 (User[]) 作為 ResultType 泛型參數(shù)的類型。data 變量現(xiàn)在的類型是 User[] 而不是 any。

注意:當(dāng)使用 await 異步處理函數(shù)的結(jié)果時(shí),返回類型將是 Promise<T> 中的 T 類型,在這個(gè)示例中就是泛型類型 ResultType。

(3)默認(rèn)類型參數(shù)

在上面 fetchApi 函數(shù)的例子中,調(diào)用代碼時(shí)必須提供類型參數(shù)。如果調(diào)用代碼不包含泛型類型參數(shù),則 ResultType 將推斷為 unknow。來看下面的例子:

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)

這段代碼嘗試訪問data的a屬性,但是由于data是unknow類型,將無法訪問對(duì)象的屬性。

如果不打算為泛型函數(shù)的每次調(diào)用添加特定的類型,則可以為泛型類型參數(shù)添加默認(rèn)類型。通過在泛型類型參數(shù)后面添加 = DefaultType 來完成:

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í)將類型傳遞給 ResultType 泛型參數(shù),因?yàn)樗哂心J(rèn)類型 Record<string, any>。這意味著 TypeScript 會(huì)將data識(shí)別為具有string類型的鍵和any類型值的對(duì)象,從而允許訪問其屬性。

(4)類型參數(shù)約束

在某些情況下,泛型類型參數(shù)只允許將某些類型傳遞到泛型中,這時(shí)就可以對(duì)參數(shù)添加約束。

假如有一個(gè)存儲(chǔ)限制,只能存儲(chǔ)所有屬性值都為字符串類型的對(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ù),可以在泛型類型 T 上使用類型約束:

function stringifyObjectKeyValues<T extends Record<string, any>>(obj: T) {
  // ...
}

extends Record<string, any> 被稱為泛型類型約束,它允許指定泛型類型必須可分配給 extends 關(guān)鍵字之后的類型。在這種情況下,Record<string, any> 表示具有string類型的鍵和any類型的值的對(duì)象。我們可以使類型參數(shù)擴(kuò)展任何有效的 TypeScript 類型。

在調(diào)用reduce時(shí),reducer函數(shù)的返回類型是基于累加器的初始值。{} as { [K in keyof T]: string } 通過對(duì)空對(duì)象 {} 使用類型斷言將累加器的初始值的類型設(shè)置為{ [K in keyof T]: string }。type { [K in keyof T]: string } 創(chuàng)建了一個(gè)新類型,其鍵與 T 相同,但所有值都設(shè)置為字符串類型,這稱為映射類型。

下面來測(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 的類型如下:

{
  a: string;
  b: string;
  c: string;
  d: string;
}

3. 在接口、類和類型中使用泛型

在 TypeScript 中創(chuàng)建接口和類時(shí),使用泛型類型參數(shù)來設(shè)置結(jié)果對(duì)象的類型會(huì)很有用。例如,一個(gè)類可能具有不同類型的屬性,具體取決于傳入構(gòu)造函數(shù)的內(nèi)容。下面就來看看在類和接口中聲明泛型類型參數(shù)的語(yǔ)法。

(1)接口和類中的泛型

要?jiǎng)?chuàng)建泛型接口,可以在接口名稱后添加類型參數(shù)列表:

interface MyInterface<T> {
  field: T
}

這里聲明了一個(gè)具有field字段的接口,field字段的類型由傳入 T 的類型確定。

對(duì)于類,它的語(yǔ)法和接口定義幾乎是相同的:

class MyClass<T> {
  field: T
  constructor(field: T) {
    this.field = field
  }
}

通用接口/類的一個(gè)常見例子就是當(dāng)有一個(gè)類型取決于如何使用接口/類的字段。假設(shè)有一個(gè) HttpApplication 類,用于處理對(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è)類儲(chǔ)存了一個(gè) context,它的類型作為 get 方法中handler函數(shù)的參數(shù)類型傳入。在使用時(shí),傳遞給 get 方法的handler的參數(shù)類型將從傳遞給類構(gòu)造函數(shù)的內(nèi)容中推斷出來:

const context = { someValue: true };
const app = new HttpApplication(context);

app.get('/api', async () => {
  console.log(context.someValue)
});

在這段代碼中,TypeScript 會(huì)將 context.someValue 的類型推斷為 boolean。

(2)自定義類型中的泛型

將泛型應(yīng)用于類型的語(yǔ)法類似于它們應(yīng)用于接口和類的方式。來看下面的代碼:

type MyIdentityType<T> = T

這個(gè)泛型類型返回類型參數(shù)傳遞的類型。使用以下代碼來實(shí)現(xiàn)這種類型:

type B = MyIdentityType<number>

在這種情況下,B 的類型就是number。

泛型類型通常用于創(chuàng)建工具類型,尤其是在使用映射類型時(shí)。TypeScript 內(nèi)置了許多工具類型。例如 Partial實(shí)用工具類型,它傳入類型 T 并返回另一種具有與 T 相同的類型,但它們的所有字段都設(shè)置為可選。Partial的實(shí)現(xiàn)如下:

type Partial<T> = {
  [P in keyof T]?: T[P];
};

這里,Partial 接受一個(gè)類型,遍歷它的屬性類型,然后將它們作為可選的新類型返回。

注意:由于 Partial 已經(jīng)內(nèi)置到了 TypeScript 中,因此將此代碼編譯到 TypeScript 環(huán)境中會(huì)重新聲明 Partial 并引發(fā)錯(cuò)誤。上面的 Partial 實(shí)現(xiàn)僅用于說明目的。

4. 使用泛型創(chuàng)建映射類型

使用 TypeScript 時(shí),有時(shí)需要?jiǎng)?chuàng)建一個(gè)與另一種類型具有相同結(jié)構(gòu)的類型。這意味著它們應(yīng)該具有相同的屬性,但屬性的類型不同。對(duì)于這種情況,使用映射類型可以重用初始類型并減少重復(fù)代碼。這種結(jié)構(gòu)稱為映射類型并依賴于泛型。下面就來看看如何創(chuàng)建映射類型。

先來看一個(gè)例子,給定一種類型,返回一個(gè)新類型,其中所有屬性值都設(shè)置為 boolean 類型??梢允褂靡韵麓a創(chuàng)建此類型:

type BooleanFields<T> = {
  [K in keyof T]: boolean;
}

在這種類型中,使用 [K in keyof T] 指定新類型將具有的屬性。keyof T 用于返回 T 中所有可用屬性的名稱。然后使用 K in 來指定新類型的屬性是keyof T返回的類型中可用的所有屬性。

這將創(chuàng)建一個(gè)名為 K 的新類型,該類型就是當(dāng)前屬性的名稱??梢杂糜谑褂?nbsp;T[K] 語(yǔ)法來訪問原始類型中此屬性的類型。在這種情況下,將屬性值的類型設(shè)置為 boolean。

這種 BooleanFields 類型的一個(gè)使用場(chǎng)景是創(chuàng)建一個(gè)選項(xiàng)對(duì)象。假設(shè)有一個(gè)數(shù)據(jù)庫(kù)模型,例如 User。從數(shù)據(jù)庫(kù)中獲取此模型的記錄時(shí),還可以傳遞一個(gè)指定要返回哪些字段的對(duì)象。該對(duì)象將具有與模型相同的屬性,但類型設(shè)置為布爾值。在字段中傳遞 true 意味著希望它被返回,而 false 則意味著希望它被省略。

可以在現(xiàn)有模型類型上使用 BooleanFields 泛型來返回與模型具有相同結(jié)構(gòu)的新類型,但所有字段都設(shè)置為布爾類型,代碼如下所示:

type BooleanFields<T> = {
  [K in keyof T]: boolean;
};

type User = {
  email: string;
  name: string;
}

type UserFetchOptions = BooleanFields<User>;

UserFetchOptions 的類型如下:

type UserFetchOptions = {
  email: boolean;
  name: boolean;
}

在創(chuàng)建映射類型時(shí),還可以為字段提供修飾符,例如 Readonly<T>。Readonly<T> 類型返回一個(gè)新類型,其中傳遞類型的所有屬性都設(shè)置為只讀屬性。這種類型的實(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)僅用于說明目的。

目前,可以在映射類型中使用的兩個(gè)可用修飾符是 readonly 修飾符,它必須作為前綴添加到屬性中,用于將屬性設(shè)置為只讀;以及?修飾符,可以作為后綴添加到屬性中,用于將屬性設(shè)置為可選。

5. 使用泛型創(chuàng)建條件類型

下面來看看如何使用泛型創(chuàng)建條件類型。

(1)基礎(chǔ)條件類型

條件類型是泛型類型,根據(jù)某些條件具有不同的結(jié)果類型。先來看看下面的泛型類型 IsStringType<T>:

type IsStringType<T> = T extends string ? true : false;

在這段代碼中,創(chuàng)建了一個(gè)名為 IsStringType 的新泛型類型,它接收一個(gè)類型參數(shù) T。在類型定義中,使用的語(yǔ)法類似于 JavaScript 中的三元表達(dá)式。此條件表達(dá)式檢查類型 T 是否是 string 類型。如果是,結(jié)果的類型將是 true;否則,結(jié)果的類型將是 false 。

要嘗試這種條件類型,需要將類型作為其類型參數(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)建了兩種類型:A 和 B。A 是字符串字面量類型 abc,B 是具有string類型的name屬性的對(duì)象的類型。將這兩種類型與 IsStringType 條件類型一起使用,并將結(jié)果類型存儲(chǔ)到兩個(gè)新類型 ResultA 和 ResultB 中。

這里 ResultA 類型設(shè)置為 true,而 ResultB 類型設(shè)置為 false。因?yàn)?nbsp;A 確實(shí)擴(kuò)展了字符串類型,而 B 沒有擴(kuò)展字符串類型,因?yàn)樗辉O(shè)置為具有字符串類型的單個(gè)name屬性的對(duì)象的類型。

條件類型的一個(gè)有用特性是它允許使用特殊關(guān)鍵字 infer 在 extends 中推斷類型信息。可以在條件為真的分支中使用這種新類型。此功能的一種用途是檢索任何函數(shù)類型的返回類型。

例如,GetReturnType 類型定義如下:

type GetReturnType<T> = T extends (...args: any[]) => infer U ? U : never;

在這段代碼中,創(chuàng)建了一個(gè)新的泛型類型,它是一個(gè)名為 GetReturnType 的條件類型。此泛型類型接受一個(gè)類型參數(shù) T。在類型聲明本身內(nèi)部,檢查類型 T 是否擴(kuò)展了與接受可變數(shù)量參數(shù)(包括0)的函數(shù)簽名匹配的類型,然后推斷該返回函數(shù)的類型,創(chuàng)建一個(gè)新類型 U,它可用于條件的真實(shí)分支。U 的類型將綁定到傳遞函數(shù)的返回值的類型。如果傳遞的類型 T 不是函數(shù),則代碼將返回類型nerver。

將此類型與以下代碼一起使用:

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ù)的類型傳遞給 GetReturnType 泛型,并將結(jié)果類型保存在 ReturnTypeOfSomeFunction 中。

由于 someFunction 變量的類型是函數(shù),因此條件類型將計(jì)算條件為真的分支。這將返回類型 U 作為結(jié)果。U類型是根據(jù)函數(shù)的返回類型推斷出來的,在本例中是boolean。如果檢查 ReturnTypeOfSomeFunction 的類型,會(huì)發(fā)現(xiàn)它被設(shè)置為了boolean類型。

(2)高級(jí)條件類型

條件類型是 TypeScript 中最靈活的功能之一,允許創(chuàng)建一些高級(jí)實(shí)用程序類型。接下來就創(chuàng)建一個(gè)名為 NestedOmit<T, KeysToOmit> 的類型,它可以省略對(duì)象中的字段,就像現(xiàn)有的 Omit<T, KeysToOmit> 實(shí)用程序類型一樣,但也允許使用點(diǎn)表示法省略嵌套字段。

使用新的 NestedOmit<T, KeysToOmit> 泛型,將能夠使用下面例子中的類型:

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 的類型,該類型具有嵌套屬性的多級(jí)結(jié)構(gòu)。使用 NestedOmit 泛型傳入類型,然后列出想要省略的屬性的key。第二個(gè)類型參數(shù)中使用點(diǎn)符號(hào)來標(biāo)識(shí)要省略的鍵。然后將結(jié)果類型存儲(chǔ)在 Result 中。

構(gòu)造此條件類型將使用 TypeScript 中的許很多功能,例如模板文本類型、泛型、條件類型和映射類型。

首先創(chuàng)建一個(gè)名為 NestedOmit 的泛型類型,它接受兩個(gè)類型參數(shù):

type NestedOmit<T extends Record<string, any>, KeysToOmit extends string>

第一個(gè)類型參數(shù)為 T,它必須是可分配給 Record<string, any> 類型的類型,它是要從中省略屬性的對(duì)象的類型。第二個(gè)類型參數(shù)為 KeysToOmit,它必須是string類型。使用它來指定要從類型 T 中省略的key。

接下來需要判斷 KeysToOmit 是否可分配給類型 ${infer KeyPart1}.${infer KeyPart2}:

type NestedOmit<T extends Record<string, any>, KeysToOmit extends string> =
  KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}`

這里就用到了模板文本類型,同時(shí)利用條件類型在模板文字中推斷出其他兩種類型。通過這兩部分,將一個(gè)字符串拆分為了兩個(gè)字符串。第一部分將分配給類型 KeyPart1 并將包含第一個(gè)點(diǎn)之前的所有內(nèi)容。第二部分將分配給類型 KeyPart2 并將包含第一個(gè)點(diǎn)之后的所有內(nèi)容。如果將“a.b.c”作為 KeysToOmit 傳遞,則最初 KeyPart1 將設(shè)置為字符串類型“a”,而 KeyPart2 將設(shè)置為“b.c”。

接下來,使用三元表達(dá)式來定義條件為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 來檢查 KeyPart1 是否是給定類型 T 的有效屬性。如果是一個(gè)有效的 key,使用以下代碼以使條件計(jì)算為兩種類型之間的交集:

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)建的類型。此時(shí),KeyPart1 不是點(diǎn)表示法:它將包含一個(gè)字段的確切名稱,該字段包含要從原始類型中省略的嵌套字段。因此,可以安全地使用現(xiàn)有的實(shí)用程序類型。

使用 Omit 刪除 T[KeyPart1] 內(nèi)的一些嵌套字段,為此,必須重新生成 T[KeyPart1] 的類型。為避免重新生成整個(gè) T 類型,使用 Omit 從 T 中僅刪除 KeyPart1,保留其他字段。然后,將在下一部分的類型中重建 T[KeyPart1]。

[NewKeys in KeyPart1]:NestedOmit<T[NewKeys], KeyPart2> 是一個(gè)映射類型,其中屬性是可分配給 KeyPart1 的屬性,也就是上面從 KeysToOmit 中提取的部分。這是需要?jiǎng)h除的字段的父級(jí)。如果傳入了 a.b.c,那么在第一次它將是a中的 NewKeys。然后,將此屬性的類型設(shè)置為遞歸調(diào)用 NestedOmit 實(shí)用程序類型的結(jié)果,但現(xiàn)在使用 T[NewKeys] 作為第一個(gè)類型參數(shù)傳遞 T 內(nèi)的此屬性的類型,并作為第二個(gè)類型參數(shù)傳遞點(diǎn)符號(hào)的其余key,在 KeyPart2 中可用。

在內(nèi)部條件為 false 分支中,返回綁定到 T 的當(dāng)前類型,就好像 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 中不存在的字段。在這種情況下,無需再進(jìn)一步。最后,在外部條件為 false的分支中,使用內(nèi)置的 Omit 實(shí)用程序類型從 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}.${infer KeyPart2} 為 false,則表示 KeysToOmit 未使用點(diǎn)表示法,因此可以使用 Omit 實(shí)用程序類型。

現(xiàn)在,要使用新的 NestedOmit 條件類型,創(chuàng)建一個(gè)名為 NestedObject 的類型:

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">;

在條件類型的第一次計(jì)算中,外部條件為真,因?yàn)樽址置媪款愋蚢.b.c可分配給模板文本類型${infer KeyPart1}.${infer KeyPart2}。在這種情況下,KeyPart1 將被推斷為字符串字面量類型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è)新類型,它是其他兩種類型的交集。第一種類型是在 T 上使用 Omit 實(shí)用程序類型來省略可分配給 KeyPart1 的字段(在本例中為 a 字段)的結(jié)果。第二種類型是通過遞歸調(diào)用 NestedOmit 構(gòu)建的新類型。

如果對(duì) NestedOmit 進(jìn)行下一步求值,對(duì)于第一次遞歸調(diào)用,交集類型會(huì)構(gòu)建一個(gè)類型以用作 a 字段的類型。這將重新創(chuàng)建a字段,而不需要忽略嵌套字段。

在 NestedOmit 的最終計(jì)算中,第一個(gè)條件將返回 false,因?yàn)閭鬟f的字符串類型是c。這種情況下,可以使用內(nèi)置類型從對(duì)象中省略該字段。這將返回 b 字段的類型,即省略 c 的原始類型。計(jì)算到此結(jié)束,TypeScript 返回了需要使用的新類型,省略了嵌套字段。

6. 小結(jié)

本文詳細(xì)解釋了適用于函數(shù)、接口、類和自定義類型的泛型,還使用泛型創(chuàng)建映射類型和條件類型。這些中的每一個(gè)都使泛型成為使用 TypeScript 時(shí)的強(qiáng)大工具。正確的使用泛型將避免一遍又一遍地重復(fù)代碼,并使編寫的類型更加靈活。

分享到:
標(biāo)簽:TypeScript
用戶無頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定