做者:王衝html
GitHub: github.com/hubvuevue
以前有系統的學過 TypeScript,而且能夠在項目中使用一些基本的類型定義,可是對於高級類型卻只知其一;不知其二,看到一些項目或者庫中寫的高級類型徹底是懵逼的狀態,因而就決定想辦法去改變這種狀態。忘記是哪位大神說過:看源碼是最好的學習方式
,因而就決定找個專門作 TypeScript 類型的庫讀讀源碼。經過同事推薦了兩個比較好的庫:utility-types
和ts-toolbelt
,權衡下utility-types
star 比較多而且用的也比較多,那就它吧,以後再對ts-toolbelt
進行解讀。git
本篇文章主要是對mapped-types.ts
文件中的類型進行解讀。github
在 Typescript 內置的類型 API 中有個 Extract
類型,SetIntersection
類型的做用於 Extract
是相同,做用都是從類型 A 中獲取可兼容類型 B 的類型,大體意思就是獲取兩個類型的交集。多用於聯合類型。typescript
內置的 Extract
類型的實現方式和 SetIntersection
是相同的json
實現數組
type SetIntersection<A, B> = A extends B ? A : never
複製代碼
示例markdown
type SetIntersectionResult = SetIntersection<'a' | 'b' | 'c', 'c' | 'b'> // 'b' | 'c'
複製代碼
上面示例結果是怎麼獲得的呢?咱們都知道條件類型做用於聯合類型上會變成分佈式條件類型,結合上面示例和源碼解釋下:app
'a' | 'b' | 'c' extends 'c' | 'b' ? 'a' | 'b' | 'c' : never =>
('a' extends 'c' | 'b' ? 'a' : never) |
('b' extends 'c' | 'b' ? 'b' : never) |
('c' extends 'c' | 'b' ? 'c' : never) =>
never | 'b' | 'c' => 'b' | 'c'
複製代碼
與 TypeScript 內置的 Exclude
類型相同,SetDifference
類型用於獲取類型 A 中不可兼容類型 B 的類型 ,大體意思是取類型 B 在類型 A 上的補集,多用於聯合類型。分佈式
實現
type SetDifference<A, B> = A extends B ? never : A
複製代碼
示例
type SetDifferenceResult = SetDifference<'a' | 'b' | 'c', 'b'> // 'a' | 'c'
複製代碼
上面示例結果是怎麼獲得的呢?其實和上一個類型的運算結果大體相同,結合示例和源碼解釋下:
'a' | 'b' | 'c' extends 'b' ? never : 'a' | 'b' | 'c' =>
('a' extends 'b' ? never : 'a') |
('b' extends 'b' ? never : 'b') |
('c' extends 'b' ? never : 'c') =>
'a' | never | 'c' => 'a' | 'c'
複製代碼
源碼裏還有個類型SetComplement
,可是實現方式和SetDifference
相同,只是約束了泛型 B 必須爲泛型 A 的子類型,具體就不分析了。
type SetComplement<A, A1 extends A> = A extends A1 ? never : A
複製代碼
SymmetricDifference
用於獲取類型 A、B 的交集在並集上的補集,多用於聯合類型。
實現
type SymmetricDifference<A, B> = SetDifference<A | B, SetIntersection<A, B>>
複製代碼
emmmm...有點繞,看個 🌰 吧
type SymmtricDifferenceResult = SymmetricDifference<
'1' | '2' | '3',
'2' | '3' | '4'
> // '1' | '4'
複製代碼
例子中兩個類型並集爲: '1' | '2' | '3' | '4'
,交集爲'2' | '3'
,所以交集在並集上的補集爲'1' | '4'
。
是怎麼作到的呢?從源碼中能夠看出來,咱們用到了SetDifference
和SetIntersection
兩個類型,而且這兩個類型是在以前實現過的,經過組合的方式造成一個功能更增強大的類型。
源碼中的解法是這樣的:經過 A|B
獲取到 A、B 類型的並集,而後再經過SetIntersection
類型獲取到 A、B 類型的交集,最後再使用SetDifference
類型求補集得出結果。
NonUndefined
類型用於過濾掉聯合類型中的 undefined 類型。
實現
type NonUndefined<T> = T extends undefined ? never : T
複製代碼
源碼中的實現是上面這樣的,下面是借用SetDifference
的實現。
type NonUndefined<T> = SetDifference<T, undefined>
複製代碼
示例
type NonUndefinedResult = NonUndefined<string | null | undefined> // string | null
複製代碼
想要看到上面效果,你須要在tsconfig.json中將strictNullChecks
設置爲true,嚴格來檢查null類型,若是不開啓的話ts就默認undefined與null是兼容的,因此就會將null類型過濾掉。
FunctionKeys
用於獲取對象類型中值爲函數的 key。
實現
type FunctionKeys<T extends object> = {
[K in keyof T]-?: NonUndefined<T[K]> extends Function ? K : never
}[keyof T]
複製代碼
源碼裏是上面這樣實現的,可是有些缺陷,在分析原理的時候再說爲何時候缺陷的。
示例
type MixedProps = {
name: string
setName: (name: string) => void
someKeys?: string
someFn?: (...args: any) => any
undef: undefined
unNull: null
}
type FunctionKeysResult = FunctionKeys<MixedProps> //"setName" | "someFn" | "undef"
複製代碼
咦,不該該是"setName" | "someFn"
麼,爲何多了兩個呢?咱們先來分析一下這個類型是怎麼實現的,在分析過程當中找 bug。
FunctionKeys
接受的是一個對象類型,所以可使用索引查詢操做符遍歷對象類型的每個 key 值,遍歷過程當中首先經過NonUndefined
過濾掉 undefined 類型,而後 extends Function,檢測可兼容 Function 類型,那麼這個 key 的值類型就是一個函數類型,可是當值類型爲 undefined 的時候,會被NonUndefined
解爲 never,然而 Function 類型是兼容 never 的。因此undef
就被保留了下來。
因而我在源碼的基礎上改了改。
type FunctionKeys<T extends object> = {
[P in keyof T]-?: SetIntersection<NonNullable<T[P]>, Function> extends never
? never
: P
}[keyof T]
複製代碼
具體思路是在遍歷過程當中先將值類型爲 undefined、null 的 key 的值類型轉爲 never,而後再與 Function 取交集,也就是說將全部值類型不是函數類型的都轉爲 never,因爲 never 類型只對自身兼容,因此再判斷值類型是否兼容 never 類型,將全部的值爲 never 類型的 key 過濾掉,最後再經過索引查詢操做符獲取到值類型的聯合類型便可。
NonFunctionKeys
用於獲取對象類型中值不爲函數的 key
實現
type NonFunctionKeys<T extends Object> = {
[P in keyof T]-?: NonUndefined<T[P]> extends Function ? never : P
}[keyof T]
複製代碼
示例
type NonFunctionKeysResult = NonFunctionKeys<MixedProps> //"name" | "someKeys" | "unNull"
複製代碼
通過FunctionKeys
類型的分析,NonFunctionKeys
類型應該就很好理解了。
在遍歷對象類型的過程當中,先使用NonUndefined
過濾掉值類型爲 undefined 的 key,而後再過濾掉值類型爲函數類型的 key,最後經過索引查詢操做符獲取到值類型的聯合類型便可。
IfEquals 是一個輔助類型函數,用於判斷兩個類型是否相同。
實現
type IfEquals<X, Y, A = X, B = never> = (<T>() => T extends X ? 1 : 2) extends <
T
>() => T extends Y ? 1 : 2
? A
: B
複製代碼
若是你瞭解一些 TS 的話可能會想到,判斷兩個類型是否相同不是直接使用雙向 extends 就能夠了嗎,這個是什麼玩意?🤔️
我想你說的雙向 extends 方式是這樣的。
type Same<X, Y> = X extends Y ? (Y extends X ? true : false) : false
複製代碼
對於上面 Same 類型函數這種寫法,實際上是有缺陷的,它沒有辦法推斷兩個類型是否絕對相同,好比說相同結構但帶有不一樣屬性修飾符的對象類型。
type X = {
name: string
age: number
}
type Y = {
readonly name: string
age: number
}
複製代碼
上面這兩個類型 Same 類型函數就沒法推斷,這種狀況下就必需要使用IfEquals
類型函數了。
示例
type SameResult = Same<X, Y> //true
type IfEqualsResult = IfEquals<X, Y> //never
複製代碼
IfEquals
類型函數的核心就是使用了延時條件類型,在兼容性推斷的時候依賴了內部類型的一致性檢查。IfEquals
內部最少依賴了兩個泛型參數,X
、Y
,在傳入X
、Y
泛型參數後,對類型進行推斷,若是能推斷出結果就返回最終的類型,不然就延時推斷過程,等待確認的類型參數傳進來後再進行類型推斷。
像IfEquals
類型函數同樣,構造一個延時條件類型很簡單,只須要構建一個函數類型而且將函數的返回值構建成依賴泛型參數的條件類型就能夠了。
type DeferConditionalType = <T>(value: T) => T extends string ? number : boolean
複製代碼
在使用DeferConditionalType
泛型的時候就會根據傳入的泛型參數延時推斷出返回值類型。
WriteableKeys
用於獲取對象類型中全部可寫的 key。
實現
export type WriteableKeys<T extends object> = {
[P in keyof T]-?: IfEquals<
{ [Q in P]: T[P] },
{ -readonly [Q in P]: T[P] },
P
>
}[keyof T]
複製代碼
示例
type Props = { readonly foo: string; bar: number }
type WriteableKeysResult = WriteableKeys<Props> // "bar"
複製代碼
從源碼中能夠看出使用了 IfEquals 函數,如今咱們已經知道 IfEquals
函數用於判斷兩個類型是否嚴格相等(不清楚的能夠看下 IfEquals 函數的解析),因此就比較好辦了。
在遍歷對象 key 的過程當中,構造兩個對象,分別是原 key 構造的對象和去掉 readonly 修飾 key 構造的對象,而且第三個參數傳入 key,做爲匹配相同的類型函數返回值,所以最終結果就是帶有 readonly 修飾的 key 的值類型都是 never,其他的 key 的值類型是 key 自己,最後再經過索引類型訪問操做符獲取到全部 key 的值類型的聯合類型。
ReadonlyKeys
用於獲取對象類型中全部被 readonly 修飾的 key。
實現
export type ReadonlyKeys<T extends object> = {
[P in keyof T]-?: IfEquals<
{ [Q in P]: T[P] },
{ -readonly [Q in P]: T[P] },
never,
P
>
}[keyof T]
複製代碼
示例
type Props = { readonly foo: string; bar: number }
type ReadonlyKeysResult = ReadonlyKeys<Props> // "foo"
複製代碼
ReadonlyKeys
的實現方式和 WriteableKeys
的實現方式基本相同,區別在於 IfEquals
函數的第3、四個參數。在 WriteableKeys
中,第三個參數是 key,第四個參數默認是 never
,而在 ReadonlyKeys
中顛倒過來了,緣由是,當兩個類型匹配成功後,則認定這兩個類型是嚴格相同的,那麼就表示當前 key 是不被 readonly 修飾的,因此在 WriteableKeys
中返回 key、在 ReadonlyKeys
中返回 never;當兩個類型匹配不成功後,則認定這兩個類型是不相同的。
RequiredKeys RequiredKeys
用於獲取對象類型中全部必選的 key。
實現
export type RequiredKeys<T extends object> = {
[P in keyof T]-?: {} extends Pick<T, P> ? never : P
}[keyof T]
複製代碼
示例
type RequiredProps = {
req: number
reqUndef: number | undefined
opt?: string
optUndef?: number | undefined
}
type RequiredKeysResult = RequiredKeys<RequiredProps> //"req" | "reqUndef"
複製代碼
RequiredKeys
中用到了 Pick,首先說下 Pick 是幹嗎的
Pick
是 Typescript 內置的泛型函數,接受兩個 T, U,第一個參數 T 是一個對象類型,第二個參數 U 是聯合類型,而且 U extends keyof T。Pick 用於過濾掉泛型 T 中不能兼容 U 的 key。
例如:
type Props = {
req: number
reqUndef: number | undefined
opt?: string
optUndef?: number | undefined
}
type result = Pick<Props, 'req' | 'opt'> // {req: number,opt?: string}
複製代碼
回到 RequiredKeys
類型函數上,在遍歷泛型 T 的 key 過程當中,借用空對象{}去 extends 處理過的 key(此時是一個只包含 key 的對象),若當前 key 是可選的,那麼必然是兼容的,不是咱們想要的返回 never,不然是必選的,返回當前 key。
OptionalKeys
用於獲取對象類型上全部可選的 key。
實現
export type OptionalKeys<T extends object> = {
[P in keyof T]-?: {} extends Pick<T, P> ? P : never
}[keyof T]
複製代碼
示例
type RequiredProps = {
req: number
reqUndef: number | undefined
opt?: string
optUndef?: number | undefined
}
type OptionalKeysResult = OptionalKeys<RequiredProps> // "opt" | "optUndef"
複製代碼
OptionalKeys
的實現方式和 RequiredKeys
基本相同,區別在於條件類型的取值是至關的,具體細節能夠看下 RequiredKeys
的實現分析。
在解讀 RequiredKeys
類型函數的時候咱們說到了 Pick
這個內置類型函數,它是根據 key 來過濾對象的 key 的,而 PickByValue
則是根據 value 的類型來過濾對象的 key。
實現
export type PickByValue<T, K> = Pick<
T,
{
[P in keyof T]-?: T[P] extends K ? P : never
}[keyof T]
>
複製代碼
示例
type PickByValueProps = {
req: number
reqUndef: number | undefined
opt?: string
}
type PickByValueResult = PickByValue<PickByValueProps, number> //{req: number; reqUndef: number | undefined; }
複製代碼
咱們來經過結果來反推一下 PickByValue
,就這個示例而言,首先咱們想要的結果是過濾掉全部值類型可兼容 number 的 key,由於是過濾,因此 PickByValue
的最外層就必然要用 Pick
來作。
type PickByValue<T, K> = Pick<T, ...>
複製代碼
因此目前要實現這個函數只須要搞定第二個參數就能夠了。由於第二個參數必然是 keyof T 的子集,因此咱們要作就是經過 value 的類型來推出可兼容 value 類型的 key。下一步就必然要遍歷 key,而且經過{}[keyof T]
來獲取最終的子集。
type PickByValue<T, K> = Pick<T, {
[P in keyof T]: ...
}[keyof T]>
複製代碼
在遍歷過程當中判斷T[P]
的類型是否兼容 K 就能夠了,最終結果就是實現的樣子。
PickByValueExact
是 PickByValue
的嚴格版
實現
export type PickByValueExact<T, ValueType> = Pick<
T,
{
[Key in keyof T]-?: [ValueType] extends [T[Key]]
? [T[Key]] extends [ValueType]
? Key
: never
: never
}[keyof T]
>
複製代碼
源碼裏面是雙向 extends,感受使用 IfEquals 更嚴格一些。
export type PickByValueExact<T, K> = Pick<
T,
{
[P in keyof T]-?: IfEquals<[K], [T[P]], P>
}[keyof T]
>
複製代碼
示例
type PickByValueProps = {
req: number
reqUndef: number | string
opt?: string
}
type PickByValueExactResult = PickByValueExact<PickByValueProps, number> //{req: number;}
複製代碼
實現思路與 PickByValue
大體相同,區別就是判斷的地方,PickByValueExact
使用 IfEquals 作嚴格匹配。
Omit
的做用就是反向 Pick,刪除泛型 A 中可匹配泛型 B 的 key。
實現
export type Omit<A, B extends keyof A> = Pick<A, Exclude<keyof A, B>>
複製代碼
type OmitProps = {
name: string
age: number
visible: boolean
sex: string | number
}
// {
// name: string;
// visible: boolean;
// sex: string | number;
// }
type OmitResult = Omit<OmitProps, 'age'>
複製代碼
反向 Pick
能夠藉助 Pick
來作,只要對 Pick
的第二個參數作處理便可。方式就是使用 Exclude
泛型函數對 keyof A、B 取補集,獲取到泛型對象 A 中過濾掉兼容泛型 B。
反向 PickByValue
,PickByValue
是隻包含,OmitByValue
是隻過濾。
實現
export type OmitByValue<T, U> = Pick<
T,
{
[P in keyof T]: T[P] extends U ? never : P
}
>
複製代碼
示例
type OmitProps = {
name: string
age: number
visible: boolean
sex: string | number
}
// {
// age: number;
// visible: boolean;
// sex: string | number;
// }
type OmitByValueResult = OmitByValue<OmitProps, string>
複製代碼
與 PickByValue
相似,只是將 extends 的結果交換了位置,就能夠實現反向操做,具體思路請看 PickByValue
的分析。
實現
export type OmitByValueExact<T, ValueType> = Pick<
T,
{
[Key in keyof T]-?: [ValueType] extends [T[Key]]
? [T[Key]] extends [ValueType]
? never
: Key
: Key
}[keyof T]
>
複製代碼
源碼裏使用雙向 extends 判斷兩個類型是否嚴格兼容,我這裏用 IfEquals
函數搞了一下。
export type OmitByValueExact<A, B> = Pick<
A,
{
[P in keyof A]-?: IfEquals<A[P], B, never, P>
}[keyof A]
>
複製代碼
示例
type OmitProps = {
name: string
age: number
visible: boolean
sex: string | number
}
// {
// name: string
// age: number
// visible: boolean
// }
type OmitByValueExactResult = OmitByValueExact<OmitProps, string | number>
複製代碼
相信看過以前的套路,聰明的你必定能想到 OmitByValueExact
的實現方式是和 PickByValueExact
的實現方式相似的,區別在於 IfEquals
類型函數結果返回值交換了位置,具體思路請看 PickByValueExact
的實現思路。
Intersection
用於獲取對象類型 key 的交集。
實現
export type Intersection<T extends object, U extends object> = Pick<
T,
Extract<keyof T, keyof U> & Extract<keyof U, keyof T>
>
複製代碼
示例
type IntersectionProps = {
name: string
age: number
visible: boolean
value: number
}
type DefaultProps = { age: number; value: number }
// {
// age: number;
// value: number;
// }
type IntersectionResult = Intersection<IntersectionProps, DefaultProps>
複製代碼
Intersection
類型函數接受<A,B>
兩個對象類型,最終獲得的是兩個對象類型 key 的交集在 A 上的 Pick。 因此咱們只要先解兩個對象類型 key 的交集,而後再對 A 進行 Pick 就 ok 了。
求交集可使用 Extract
泛型函數,將 A、B 使用索引操做符將 key 轉爲聯合類型,而後使用 Extract
求兩個聯合類型的交集,最後對 A 進行 Pick 便可。
我的認爲第二個 Extract
是沒有必要的由於對兩個聯合類型求交集,誰先誰後兩個結果都是同樣的。
Diff
類型函數接受兩個泛型變量 T、U,且 T、U 都是對象類型,用於獲取泛型 U 在泛型 T 上的補集。
實現
export type Diff<T extends object, U extends object> = Pick<
T,
Exclude<keyof T, keyof U>
>
複製代碼
示例
type Props = {
name: string
age: number
visible: boolean
value: number
}
type Props2 = { age: number; value: number }
// {
// name: string;
// visible: boolean;
// }
type DiffResult = Diff<Props, Props2>
複製代碼
通過上面類型函數中對 Pick
函數的應用,咱們應該已經知道 Pick
是用來處理對象類型,並返回對象類型的子集,所以求補集就應該從兩個對象類型的 key 下手。開始已經提到 Exclude
用於求兩個聯合類型的補集,所以就能夠經過索引類型修飾符獲取到兩個對象類型的 key 的聯合類型,而後再經過 Exclude
取補集,最後經過 Pick 取 T 的子集便可。
Overwrite
接收兩個泛型參數 T、U,且都爲對象類型,做用是若 U 中屬性在 T 中也存在,則覆蓋 T 中的屬性。
實現
export type Overwrite<
T extends object,
U extends Object,
I = Diff<T, U> & Intersection<U, T>
> = Pick<I, keyof I>
複製代碼
示例
type Props1 = { name: string; age: number; visible: boolean }
type Props2 = { age: string; other: string }
// {
// name: string
// age: string
// visible: boolean
// }
type OverwriteResult = Overwrite<Props1, Props2>
複製代碼
若是對 Diff
、Intersection
這兩個泛型函數了解的話,那麼 Overwrite
就小菜一碟了。咱們知道 Diff
用於獲取兩個泛型參數的補集,Intersection
用於獲取兩個泛型參數的交集,最後合成交叉類型便可。
你可能會疑問,結果直接Diff<T, U> & Intersection<U, T>
就能夠了,爲何還要使用 Pick 多一次遍歷呢?
咱們分別用兩種狀況看一下類型推斷結果。
type OverwriteResult = Overwrite<Props1, Props2>
// =>
// {
// name: string
// age: string
// visible: boolean
// }
複製代碼
export type Overwrite<T extends object, U extends Object> = Diff<T, U> &
Intersection<U, T>
type OverwriteResult = Overwrite<Props1, Props2>
// => Pick<OverwriteProps, "name" | "visible"> & Pick<NewProps, "age">
複製代碼
能夠看出不使用 Pick
的結果對於用戶是不友好的,沒法直接從 IDE 中看到類型推斷的結果。
Assign
比 Overwrite
的能力更強大一些。它接收兩個泛型參數 T、U,且都爲對象類型,做用是若 U 中的屬性在 T 中存在則覆蓋,不存在則添加。
實現
export type Assign<
T extends object,
U extends object,
I = Diff<T, U> & Intersection<U, T> & Diff<U, T>
> = Pick<I, keyof I>
複製代碼
示例
type Props1 = { name: string; age: number; visible: boolean }
type Props2 = { age: string; other: string }
// {
// name: string;
// age: string;
// visible: boolean;
// other: string;
// }
type AssignResult = Assign<Props1, Props2>
複製代碼
Assign
在實現上與 Overwrite
區別是在處理 I 上比 Overwrite
多&了Diff<U, T>
,Overwrite
的做用是覆蓋已有元素,那麼實現 Assign
只須要將在 T 上不存在的屬性合併到 T 上就 ok 了,所以就可使用Diff<U, T>
的方式獲取到在 U 上而再也不 T 上的屬性,最後與前面和爲交叉類型。
Unionize
接收一個泛型參數,且爲對象類型,做用是將對象類型轉爲單獨 key 對象的聯合類型。
實現
export type Unionize<T extends object> = {
[P in keyof T]: { [Q in P]: T[P] }
}[keyof T]
複製代碼
示例
type Props = { name: string; age: number; visible: boolean }
// {
// name: string;
// } | {
// age: number;
// } | {
// visible: boolean;
// }
type UnionizeResult = Unionize<Props>
複製代碼
起初看到這個是懵逼的,而後仔細想一下,發現已經寫過不少這種方式了,直接遍歷對象 key,而後將 value 構形成對象,最後在經過索引操做符取全部值的聯合類型就能夠了。
PromiseType 用於獲取 Promise 的泛型類型。
實現
export type PromiseType<T extends Promise<unknown>> = T extends Promise<infer V>
? V
: never
複製代碼
示例
// string
type PromiseTypeResult = PromiseType<Promise<string>>
複製代碼
PromiseType
中用到了 infer,infer 的做用是在條件類型中作延時推斷,infer 用到絕佳能夠實現強大的功能。
PromiseType
將泛型 T extends Promise,並在 Promise 泛型類型使用 infer 推斷其類型,若 T 爲 Promise 類型,則 V 就是 Promise 的泛型類型,不然爲 never。
思考一下,若是深度解析 Promise 泛型呢? 🤔
utility-types
中DeepX
遞歸類型基本上相同,X
的邏輯在上面已經分析過了,主要分析是 Deep
邏輯。
實現
export type DeepReadonly<T> = T extends ((...args: any[]) => any) | Primitive
? T
: T extends _DeepReadonlyArray<infer U>
? _DeepReadonlyArray<U>
: T extends _DeepReadonlyObject<infer V>
? _DeepReadonlyObject<V>
: T
export interface _DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {}
export type _DeepReadonlyObject<T> = {
readonly [P in keyof T]: DeepReadonly<T[P]>
}
複製代碼
示例
type Props = {
first?: {
second?: {
name?: string
}
}
}
type DeepReadonlyResult = DeepReadonly<Props>
複製代碼
源碼中分別對數組和對象類型作了處理,能夠看到_DeepReadonlyObject
泛型函數在遍歷 T 的過程當中再次調用DeepReadonly
進行遞歸解析。
思考一下,爲何沒有循環引用呢? 🤔
Optional
接收兩個泛型參數 T、K,且 T 爲對象類型,K 爲 T 全部 key 聯合類型的子集,做用是 T 中可兼容 K 的屬性轉換爲可選的,默認是所有。
實現
export type Optional<
T extends object,
K extends keyof T = keyof T,
I = Omit<T, K> & Partial<Pick<T, K>>
> = Pick<I, keyof I>
複製代碼
示例
type Props = {
first: string
second: number
third: boolean
}
// {
// first?: string
// second?: number
// third: boolean
// }
type OptionsalResult = Optional<Props, 'first' | 'second'>
複製代碼
咱們能夠先想一下,要怎麼作才能實現這樣的功能。
既然要處理部分屬性,因此咱們能夠先將這部分屬性刪除,等處理好了以後再合併過來,沒錯,源碼就是這麼幹的。
若是你是按照順序讀下來的,確定已經 Omit、Pick 這兩個泛型函數的做用了(Omit 只刪除、Pick 只保留,忘了的話能夠翻上去看看),所以咱們就能夠先使用 Omit 將將要處理的屬性先刪除,而後使用 Pick 只保留將要處理的屬性並使用 Partial 泛型函數處理,最後再使用交叉類型將兩者合併起來。
ValuesType
接收一個泛型參數,能夠是數組或對象,用於獲取值的聯合類型。數組在這裏較多的指元組,由於普通數組全部元素的類型相同,就不必聯合了。
實現
export type ValuesType<
T extends Array<any> | ReadonlyArray<any> | ArrayLike<any> | object
> = T extends Array<any> | ReadonlyArray<any> | ArrayLike<any>
? T[number]
: T extends object
? T[keyof T]
: never
複製代碼
示例
type Props = {
first: string
second: number
third: boolean
}
// string | number | boolean
type ValuesTypeResult = ValuesType<Props>
複製代碼
ValuesType
處理參數主要分爲兩部分:對數組的處理和對對象的處理。對數組的處理使用T[number]
很是優雅,而且是元組類型轉聯合類型最簡單的方式;對對象的處理用的就比較多了,使用索引操做符就能夠了。
ArgumentsRequired
與 Optional
相似,用於將對象的某些屬性變成必選的
實現
export type ArgumentsRequired<
T extends object,
K extends keyof T = keyof T,
I = Omit<T, K> & Required<Pick<T, K>>
> = Pick<I, keyof I>
複製代碼
示例
type Props = {
name?: string
age?: number
visible?: boolean
}
// {
// name: string
// age: number
// visible: boolean
// }
type ArgumentsRequiredResult = ArgumentsRequired<Props>
複製代碼
實現方式的解析能夠看 Optional
,這裏就很少說了。
在 ValuesType
中已經提到一個特別簡單的方式。還有一種方式也值得學習一下。
在類型系統中,元組類型是兼容數組類型的。
// 'true'
type ret = [number, string] extends Array<any> ? 'true' : 'false'
複製代碼
所以就可使用 infer 來推斷出數組的泛型類型。
實現
export type TupleToUnion<T extends any[]> = T extends Array<infer U> ? U : never
複製代碼
示例
// string | number
type TupleToUnionResult = TupleToUnion<[string, number]>
複製代碼
UnionToIntersection 用於將聯合類型轉爲交叉類型
實現
export type UnionToIntersection<T> = (T extends any
? (arg: T) => void
: never) extends (arg: infer V) => void
? V
: never
複製代碼
示例
type UnionToIntersectionResult = UnionToIntersection<
{ name: string } | { age: number } | { visible: boolean }
>
複製代碼
UnionToIntersection 這個泛型函數仍是要好好理解的,這裏用到了 TypeScript 類型系統中的概念,同一類型變量的多個候選類型將會被推斷爲交叉類型
,這是 TS 類型系統函數參數位置逆變的知識。逆變與協變這篇文章說的很清晰,能夠深刻了解一下。
瞭解了TS類型系統後,UnionToIntersection
就比較好了解了。已知泛型函數接受的是一個聯合類型,經過分佈式條件類型構建同一類型變量多個候選類型,而後再使用延時推斷獲取到 V 的類型。
解讀utility-types
中的高級類型,我發現 TypeScript
遠遠不止咱們在函數參數位置賦予一個類型那麼簡單,必定要善用 TypeScript
類型推斷的能力,有時候你會發現,讓一個函數具有良好的類型推斷能力寫的類型居然比運行代碼還長,爲了讓代碼更穩定,更能被同事理解,有時咱們也必須這樣作😭,別讓你的 TypeScript
成爲 AnyScript
。