做者:王冲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
。