掌握 TypeScript 這些官方工具類型,讓你的開發(fā)事半功倍

我們深入了解 TypeScript 官方提供的全局工具類型。

在 TypeScript 中提供了許多自帶的工具類型,因為這些類型都是全局可用的,所以無須導(dǎo)入即可直接使用。了解了基礎(chǔ)的工具類型后,我們不僅知道 TypeScript 如何利用前幾講介紹的基礎(chǔ)類型知識實現(xiàn)這些工具類型,還知道如何更好地利用這些基礎(chǔ)類型,以免重復(fù)造輪子,并能通過這些工具類型實現(xiàn)更復(fù)雜的類型。

根據(jù)使用范圍,我們可以將工具類型劃分為操作接口類型、聯(lián)合類型、函數(shù)類型、字符串類型這 4 個方向,下面一一介紹。

操作接口類型

Partial

Partial 工具類型可以將一個類型的所有屬性變?yōu)榭蛇x的,且該工具類型返回的類型是給定類型的所有子集,下面我們看一個具體的示例:

type Partial<T> = {

? [P in keyof T]?: T[P];

};

interface Person {

? name: string;

? age?: number;

? weight?: number;

}

type PartialPerson = Partial<Person>;

// 相當(dāng)于

interface PartialPerson {

? name?: string;

? age?: number;

? weight?: number;

}

在上述示例中,我們使用映射類型取出了傳入類型的所有鍵值,并將其值設(shè)定為可選的。

Required

與 Partial 工具類型相反,Required 工具類型可以將給定類型的所有屬性變?yōu)楸靥畹模旅嫖覀兛匆粋€具體示例。

type Required<T> = {

? [P in keyof T]-?: T[P];

};

type RequiredPerson = Required<Person>;

// 相當(dāng)于

interface RequiredPerson {

? name: string;

? age: number;

? weight: number;

}

在上述示例中,映射類型在鍵值的后面使用了一個 - 符號,- 與 ? 組合起來表示去除類型的可選屬性,因此給定類型的所有屬性都變?yōu)榱吮靥睢?/p>

Readonly

Readonly 工具類型可以將給定類型的所有屬性設(shè)為只讀,這意味著給定類型的屬性不可以被重新賦值,下面我們看一個具體的示例。

type Readonly<T> = {

? readonly [P in keyof T]: T[P];

};

type ReadonlyPerson = Readonly<Person>;

// 相當(dāng)于

interface ReadonlyPerson {

? readonly name: string;

? readonly age?: number;

? readonly weight?: number;

}

在上述示例中,經(jīng)過 Readonly 處理后,ReadonlyPerson 的 name、age、weight 等屬性都變成了 readonly 只讀。

Pick

Pick 工具類型可以從給定的類型中選取出指定的鍵值,然后組成一個新的類型,下面我們看一個具體的示例。

type Pick<T, K extends keyof T> = {

? [P in K]: T[P];

};

type NewPerson = Pick<Person, 'name' | 'age'>;

// 相當(dāng)于

interface NewPerson {

? name: string;

? age?: number;

}

在上述示例中,Pick工具類型接收了兩個泛型參數(shù),第一個 T 為給定的參數(shù)類型,而第二個參數(shù)為需要提取的鍵值 key。有了參數(shù)類型和需要提取的鍵值 key,我們就可以通過映射類型很容易地實現(xiàn) Pick 工具類型的功能。

Omit

與 Pick 類型相反,Omit 工具類型的功能是返回去除指定的鍵值之后返回的新類型,下面我們看一個具體的示例:

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

type NewPerson = Omit<Person, 'weight'>;

// 相當(dāng)于

interface NewPerson {

? name: string;

? age?: number;

}

在上述示例中,Omit 類型的實現(xiàn)使用了前面介紹的 Pick 類型。我們知道 Pick 類型的作用是選取給定類型的指定屬性,那么這里的 Omit 的作用應(yīng)該是選取除了指定屬性之外的屬性,而 Exclude 工具類型的作用就是從入?yún)?T 屬性的聯(lián)合類型中排除入?yún)?K 指定的若干屬性。

Tips:操作接口類型這一小節(jié)所介紹的工具類型都使用了映射類型。通過映射類型,我們可以對原類型的屬性進行重新映射,從而組成想要的類型。


聯(lián)合類型

Exclude

在介紹 Omit 類型的實現(xiàn)中,我們使用了 Exclude 類型。通過使用 Exclude 類型,我們從接口的所有屬性中去除了指定屬性,因此,Exclude 的作用就是從聯(lián)合類型中去除指定的類型。

下面我們看一個具體的示例:

type Exclude<T, U> = T extends U ? never : T;

type T = Exclude<'a' | 'b' | 'c', 'a'>; // => 'b' | 'c'

type NewPerson = Omit<Person, 'weight'>;

// 相當(dāng)于

type NewPerson = Pick<Person, Exclude<keyof Person, 'weight'>>;

// 其中

type ExcludeKeys = Exclude<keyof Person, 'weight'>; // => 'name' | 'age'

在上述示例中,Exclude 的實現(xiàn)使用了條件類型。如果類型 T 可被分配給類型 U ,則不返回類型 T,否則返回此類型 T ,這樣我們就從聯(lián)合類型中去除了指定的類型。

再回看之前的 NewPerson 類型的例子,我們也就很明白了。在 ExcludeKeys 中,如果 Person 類型的屬性是我們要去除的屬性,則不返回該屬性,否則返回其類型。

Extract

Extract 類型的作用與 Exclude 正好相反,Extract 主要用來從聯(lián)合類型中提取指定的類型,類似于操作接口類型中的 Pick 類型。

下面我們看一個具體的示例:

type Extract<T, U> = T extends U ? T : never;

type T = Extract<'a' | 'b' | 'c', 'a'>; // => 'a'

通過上述示例,我們發(fā)現(xiàn) Extract 類型相當(dāng)于取出兩個聯(lián)合類型的交集。

此外,我們還可以基于 Extract 實現(xiàn)一個獲取接口類型交集的工具類型,如下示例:

type Intersect<T, U> = {

? [K in Extract<keyof T, keyof U>]: T[K];

};

interface Person {

? name: string;

? age?: number;

? weight?: number;

}

interface NewPerson {

? name: string;

? age?: number;

}

type T = Intersect<Person, NewPerson>;

// 相當(dāng)于

type T = {

? name: string;

? age?: number;

};

在上述的例子中,我們使用了 Extract 類型來提取兩個接口類型屬性的交集,并使用映射類型生成了一個新的類型。

NonNullable

NonNullable 的作用是從聯(lián)合類型中去除 null 或者 undefined 的類型。如果你對條件類型已經(jīng)很熟悉了,那么應(yīng)該知道如何實現(xiàn) NonNullable 類型了。

下面看一個具體的示例:

type NonNullable<T> = T extends null | undefined ? never : T;

// 等同于使用 Exclude

type NonNullable<T> = Exclude<T, null | undefined>;

type T = NonNullable<string | number | undefined | null>; // => string | number

在上述示例中,如果 NonNullable 傳入的類型可以被分配給 null 或是 undefined ,則不返回該類型,否則返回其具體類型。

Record

Record 的作用是生成接口類型,然后我們使用傳入的泛型參數(shù)分別作為接口類型的屬性和值。

下面我們看一個具體的示例:

type Record<K extends keyof any, T> = {

? [P in K]: T;

};

type MenuKey = 'home' | 'about' | 'more';

interface Menu {

? label: string;

? hidden?: boolean;

}

const menus: Record<MenuKey, Menu> = {

? about: { label: '關(guān)于' },

? home: { label: '主頁' },

? more: { label: '更多', hidden: true },

};

在上述示例中,Record 類型接收了兩個泛型參數(shù):第一個參數(shù)作為接口類型的屬性,第二個參數(shù)作為接口類型的屬性值。

需要注意:這里的實現(xiàn)限定了第一個泛型參數(shù)繼承自keyof any。

在 TypeScript 中,keyof any 指代可以作為對象鍵的屬性,如下示例:

type T = keyof any; // => string | number | symbol

說明:目前,JavaScript 僅支持string、number、symbol作為對象的鍵值。


函數(shù)類型

ConstructorParameters

ConstructorParameters 可以用來獲取構(gòu)造函數(shù)的構(gòu)造參數(shù),而 ConstructorParameters 類型的實現(xiàn)則需要使用 infer 關(guān)鍵字推斷構(gòu)造參數(shù)的類型。

關(guān)于 infer 關(guān)鍵字,我們可以把它當(dāng)成簡單的模式匹配來看待。如果真實的參數(shù)類型和 infer 匹配的一致,那么就返回匹配到的這個類型。

下面看一個具體的示例:

type ConstructorParameters<T extends new (...args: any) => any> = T extends new (

? ...args: infer P

) => any

? ? P

? : never;

class Person {

? constructor(name: string, age?: number) {}

}

type T = ConstructorParameters<typeof Person>; // [name: string, age?: number]

在上述示例中,ConstructorParameters 泛型接收了一個參數(shù),并且限制了這個參數(shù)需要實現(xiàn)構(gòu)造函數(shù)。于是,我們通過 infer 關(guān)鍵字匹配了構(gòu)造函數(shù)內(nèi)的構(gòu)造參數(shù),并返回了這些參數(shù)。因此,可以看到第 7 行匹配了 Person 構(gòu)造函數(shù)的兩個參數(shù),并返回了一個元組類型 [string, number] 給類型別名 T。

Parameters

Parameters 的作用與 ConstructorParameters 類似,Parameters 可以用來獲取函數(shù)的參數(shù)并返回序?qū)Γ缦率纠?/p>

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

type T0 = Parameters<() => void>; // []

type T1 = Parameters<(x: number, y?: string) => void>; // [x: number, y?: string]

在上述示例中,Parameters 的泛型參數(shù)限制了傳入的類型需要滿足函數(shù)類型。

ReturnType

ReturnType 的作用是用來獲取函數(shù)的返回類型,下面我們看一個具體的示例:

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

type T0 = ReturnType<() => void>; // => void

type T1 = ReturnType<() => string>; // => string

在上述示例中,ReturnType的泛型參數(shù)限制了傳入的類型需要滿足函數(shù)類型。

ThisParameterType

ThisParameterType 可以用來獲取函數(shù)的 this 參數(shù)類型,下面看一個具體的示例:

type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown;

type T = ThisParameterType<(this: Number, x: number) => void>; // Number

在上述示例的第 1 行中,因為函數(shù)類型的第一個參數(shù)聲明的是 this 參數(shù)類型,所以我們可以直接使用 infer 關(guān)鍵字進行匹配并獲取 this 參數(shù)類型。在示例的第 2 行,類型別名 T 得到的類型就是 Number。

ThisType

ThisType 的作用是可以在對象字面量中指定 this 的類型。ThisType 不返回轉(zhuǎn)換后的類型,而是通過 ThisType 的泛型參數(shù)指定 this 的類型,下面看一個具體的示例:

注意:如果你想使用這個工具類型,那么需要開啟noImplicitThis的 TypeScript 配置。

type ObjectDescriptor<D, M> = {

? data?: D;

? methods?: M & ThisType<D & M>; // methods 中 this 的類型是 D & M

};

function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {

? let data: object = desc.data || {};

? let methods: object = desc.methods || {};

? return { ...data, ...methods } as D & M;

}

const obj = makeObject({

? data: { x: 0, y: 0 },

? methods: {

? ? moveBy(dx: number, dy: number) {

? ? ? this.x += dx; // this => D & M

? ? ? this.y += dy; // this => D & M

? ? },

? },

});

obj.x = 10;

obj.y = 20;

obj.moveBy(5, 5);

在上述示例子中,methods 屬性的 this 類型為 D & M,在上下文中指代 { x: number, y: number } & { moveBy(dx: number, dy: number): void }。

ThisType 工具類型只是提供了一個空的泛型接口,僅可以在對象字面量上下文中被 TypeScript 識別,如下所示:

interface ThisType<T> {}

也就是說該類型的作用相當(dāng)于任意空接口。

OmitThisParameter

OmitThisParameter 工具類型主要用來去除函數(shù)類型中的 this 類型。如果傳入的函數(shù)類型沒有顯式聲明 this 類型,那么返回的仍是原來的函數(shù)類型。

下面看一個具體的示例:

type OmitThisParameter<T> = unknown extends ThisParameterType<T>

? ? T

? : T extends (...args: infer A) => infer R

? ? (...args: A) => R

? : T;

type T = OmitThisParameter<(this: Number, x: number) => string>; // (x: number) => string

在上述示例中, ThisParameterType 類型的實現(xiàn)如果傳入的泛型參數(shù)無法推斷 this 的類型,則會返回 unknown 類型。在OmitThisParameter 的實現(xiàn)中,第一個條件語句如果傳入的函數(shù)參數(shù)沒有 this 類型,則返回原類型;否則通過 infer 分別獲取函數(shù)參數(shù)和返回值的類型構(gòu)造一個新的沒有 this 的函數(shù)類型,并返回這個函數(shù)類型。


字符串類型

模板字符串

TypeScript 自 4.1版本起開始支持模板字符串字面量類型。為此,TypeScript 也提供了 Uppercase、Lowercase、Capitalize、Uncapitalize這 4 種內(nèi)置的操作字符串的類型,如下示例:

// 轉(zhuǎn)換字符串字面量到大寫字母

type Uppercase<S extends string> = intrinsic;

// 轉(zhuǎn)換字符串字面量到小寫字母

type Lowercase<S extends string> = intrinsic;

// 轉(zhuǎn)換字符串字面量的第一個字母為大寫字母

type Capitalize<S extends string> = intrinsic;

// 轉(zhuǎn)換字符串字面量的第一個字母為小寫字母

type Uncapitalize<S extends string> = intrinsic;

type T0 = Uppercase<'Hello'>; // => 'HELLO'

type T1 = Lowercase<T0>; // => 'hello'

type T2 = Capitalize<T1>; // => 'Hello'

type T3 = Uncapitalize<T2>; // => 'hello'

在上述示例中,這 4 種操作字符串字面量工具類型的實現(xiàn)都是使用 JavaScript 運行時的字符串操作函數(shù)計算出來的,且不支持語言區(qū)域設(shè)置。以下代碼是這 4 種字符串工具類型的實際實現(xiàn)。

function applyStringMapping(symbol: Symbol, str: string) {

? switch (intrinsicTypeKinds.get(symbol.escapedName as string)) {

? ? case IntrinsicTypeKind.Uppercase:

? ? ? return str.toUpperCase();

? ? case IntrinsicTypeKind.Lowercase:

? ? ? return str.toLowerCase();

? ? case IntrinsicTypeKind.Capitalize:

? ? ? return str.charAt(0).toUpperCase() + str.slice(1);

? ? case IntrinsicTypeKind.Uncapitalize:

? ? ? return str.charAt(0).toLowerCase() + str.slice(1);

? }

? return str;

}

在上述代碼中可以看到,字符串的轉(zhuǎn)換使用了 JavaScript 中字符串的 toUpperCase 和 toLowerCase 方法,而不是 toLocaleUpperCase 和 toLocaleLowerCase。其中 toUpperCase 和 toLowerCase 采用的是 Unicode 編碼默認(rèn)的大小寫轉(zhuǎn)換規(guī)則。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內(nèi)容