携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第14天,点击查看活动详情
在TypeScript中,除了有一些和js重叠的类型,如number、string、boolean等,但也有一些是ts特有的,比如any、never、联合类型,这些特殊类型有它们自己的特性,当我们需要判断一个类型参数是否是这些特殊类型时,就可以根据它们各自的特性去实现判断逻辑
除此之外,在TypeScript的世界里,类class也相较js做了扩展,属性可以用private、protected、public等修饰符来描述成员属性的可见性,同样地,它们也有自己的特性,我们可以巧用这些特性去实现工具类型,判断一个属性是否是类的私有属性等功能
接下来我就会巧用这些特性,实现上述的这些工具类型
IsAny -- 判断类型是否是any
假设现在有下面这个需求,让你实现这样一个工具类型,接收一个泛型参数,判断传入的泛型是不是any类型
IsAny<any> // ==> true
IsAny<number> // ==> false
IsAny<string> // ==> false
这就需要利用到any的特性了
- 在
ts中,any和任何类型做交叉运算,得到的仍然是any
type A = any & 1 // any
type B = any & number // any
type C = any & string // any
type D = any & boolean // any
// ...
- 任何类型都是
any的子类型,可以通过extends进行类型约束验证这一点
type A = number extends any ? true : false // true
type B = string extends any ? true : false // true
type C = boolean extends any ? true : false // true
type D = 1 extends any ? true : false // true
type E = 'plasticine' extends any ? true : false // true
于是我们就可以利用这两个特性,去实现IsAny,怎么实现呢?
根据特性2,我们可以用条件类型判断,条件为判断任意一个类型是否extends泛型参数和任意类型交叉运算的结果,如果是的话,说明泛型参数是any,因为只有any与类型交叉运算后得到的才会是any,然后判断任意类型 extends any为true就说明泛型参数是any
这里的任意类型我们可以随便选一个,只要不是any就行,主要是起到一个占位的作用
type IsAny<T> = number extends T & number ? true : false
IsEqual -- 判断两个类型是否相等(包括对any的判断)
如何判断两个类型是否是同一个类型呢?一个简单的实现可能是下面这样:
type IsEqual<A, B> = (A extends B ? true : false) & (B extends A ? true : false)
type Res = IsEqual<string, number> // false
type Res1 = IsEqual<'1', '2'> // false
type Res2 = IsEqual<true, true> // true
type Res3 = IsEqual<'a', any> // true
可以看到,前三个都没啥问题,但是最后一个IsEqual<'a', any>的结果是false,可实际上类型'a'不应当和any是同一个类型,它们只有相关性,也就是'a'属于any,而any也可以代表'a'类型,所以这样的写法是无法判断出any类型的相等性的,我们目前要判断的是两个类型是否相等,而不是相关
这就先要聊聊ts源码级层面的内容了,在ts中,先看看两个条件类型之间的判断:
(T1 extends U1 ? X1 : Y1) extends (T2 extends U2 ? X2: Y2) ? true : false
对于两个条件类型的判断结果,在ts源码中的判断逻辑是
T1和T2相关X1和X2相关Y1和Y2相关U1和U2相等
类型相关就比如1 extends number是true,但是1和number并不相等
可以看到,虽然ts没有提供判断两个类型相等的途径,但是源码层面上是有这样一个地方可以判断两个类型严格相等的,而我们恰好就可以利用这个特性去判断两个类型是否相等,只要把待判断的两个类型放到U1和U2的位置即可
/**
* @description 判断两个类型是否相等 -- 是相等不是相关
*
* 相等和相关的区别 -- 1 和 any 相关 因为 1 extends any 是 true
* 但是如果要说 1 和 any 是同一个类型吗?那显然是不对的
* 所以我们的 IsEqual<1, any> 应为 false 而不是 true
*/
// 无法判断 any 类型
// type IsEqual<A, B> = (A extends B ? true : false) & (B extends A ? true : false)
// 利用 extends 判断两个条件类型时会有严格相等判断的逻辑实现类型相等判断
// (T1 extends U1 ? X1 : Y1) extends (T2 extends U2 ? X2 : Y2) ? true : false
// T1 和 T2 相关
// X1 和 X2 相关
// Y1 和 Y2 相关
// U1 和 U2 相等
// 满足这四个条件时才会是 true
type IsEqual<A, B> = (<T>() => T extends A ? true : false) extends <
T,
>() => T extends B ? true : false
? true
: false
type Res = IsEqual<string, number> // false
type Res1 = IsEqual<'1', '2'> // false
type Res2 = IsEqual<true, true> // true
type Res3 = IsEqual<'a', any> // true
IsUnion -- 判断类型是否是联合类型
首先要了解联合类型的特性,当联合类型作为extends关键字的左边部分时,联合类型是会被拆分成单独元素传入的,所以可以利用这点来判断一个类型是否是联合类型
// 利用联合类型作为 extends 的左边部分时会被拆开传入的特性来判断 A 是否是联合类型
type IsUnion<A, B = A> = A extends A ? ([B] extends [A] ? false : true) : never
type Res = IsUnion<'a' | 'b'> // true
type Res1 = IsUnion<'a'> // false
IsNever -- 判断类型是否是never
never的特性:当作为条件类型extends左边的部分时,条件类型的结果直接就是never
type IsNever<T> = T extends never ? true : false
type Res = IsNever<never> // never
但是现在我们要的不是never,而是true和false,这时候就可以改变一下,把类型参数用数组包起来,变成一个元组类型,从而得到布尔值的结果
type IsNever<T> = [T] extends [never] ? true : false
type Res = IsNever<never> // true
type Res1 = IsNever<1> // false
IsTuple -- 判断类型是否是元组类型
一个简单的想法可能是下面这样,直接判断元素是否是一个数组
type IsTuple<T> = T extends [...els: unknown[]] ? true : false
type Res = IsTuple<number[]> // true
type Res1 = IsTuple<[1, 2, 3]> // true
看上去好像没毛病,但实际上,number[]是数组类型,而[1, 2, 3]则是元组类型,二者有何区别呢?
- 数组类型长度不固定,通过
['length']索引访问其长度时,得到的是number类型 - 元组类型长度固定,通过
['length']索引访问其长度时,得到的是具体的数字,比如1、2等 - 元组类型的元素都是
readonly的,也就意味着不能够修改元组中的元素
可以在上面那个实现的基础上,添加对readonly的判断以及对length属性是否为number的判断,即可实现对元组类型的判断
// 判断两个类型不相等
type NotEqual<A, B> = (<T>() => T extends A ? true : false) extends <
T,
>() => T extends B ? true : false
? false
: true
type IsTuple<T> = T extends [...els: infer Els]
? NotEqual<Els['length'], number>
: false
type Res = IsTuple<number[]> // false
type Res1 = IsTuple<[1, 2, 3]> // true
NotEqual的实现就是在IsEqual的基础上,对调最后的布尔值位置即可实现取反的效果