TS类型技巧(六):特殊类型
1. IsAny
any 与任何类型的交叉类型都是 any
type IsAny<T> = 0 extends (2 & T) ? true : false
0 和 1 可以换成任意两个不同的类型
2. IsEqual
// 之前的简易实现
type IsEqual0<A, B> = (A extends B ? true : false) &
(B extends A ? true : false)
// true, 无法判别any
type isEqualRes0 = IsEqual0<'a', any>
// 最终实现
type IsEqual<A, B> =
(<T>() => T extends A ? 1 : 2) extends
(<T>() => T extends B ? 1 : 2)
? true : false;
// false
type isEqualRes1 = IsEqual<'a', any>
IsEqual
中 any
的万能特性似乎失效了
函数参数的双重泛型分发差异, IsEqual
中
这是比较两个泛型函数签名的 assignability(能否赋值给彼此)。
当 A 是 any 时,T extends A 会总是 true,所以整个函数返回类型恒为 1。
而 T extends B 就看 B 是否为 any 或其它类型,结果是变化的。
函数类型的判断是整体结构相同且参数类型相容才算相等;
在泛型中加入条件逻辑后,any 的透明性会失效。
type F1 = <T>() => T extends any ? 1 : 2; // always 1
type F2 = <T>() => T extends string ? 1 : 2;
type Test = F1 extends F2 ? true : false; // false ✅
如果 F1 是完全相同的结构,它应该被认为是 F2 的子类型;
但由于 F1 总是返回 1,而 F2 的行为依赖于 T,因此两者不等。
这就是 any 被“识破”的机制。
3. NotEqual
type NotEqual<A, B> =
(<T>() => T extends A ? 1 : 2) extends
(<T>() => T extends B ? 1 : 2)
? false : true
4. IsUnion
type IsUnion<A, B = A> = A extends A
? [B] extends [A]
? false
: true
: never
5. IsNever
type IsNever<T> = [T] extends [never] ? true : false
如果条件类型左边是never, 那永远返回never
type BadIsNever<T> = T extends never ? true : false;
// never
type testBadIsNever = BadIsNever<never>
6. IsTuple
元组类型的 length 是数字字面量,而数组的 length 是 number
type IsTuple<T> = T extends [...params: infer args]
? NotEqual<args['length'], number>
: false
type NotEqual<A, B> =
(<T>() => T extends A ? 1 : 2) extends
(<T>() => T extends B ? 1 : 2)
? false : true
// true
type test9 = IsTuple<[1, 2, 3]>
7. UnionToIntersection
函数参数处会发生逆变,可以用来实现联合类型转交叉类型。
联合类型转交叉类型
type UnionToIntersection<U> =
(U extends U ? (x: U) => unknown : never) extends
(x: infer R) => unknown
? R // { [K in keyof R]: R[K] }
: never
// { a: string; } & { b: number; }
type testUnionToIntersection =
UnionToIntersection<{ a: string } | { b: number }>
U extends U
是为了触发联合类型的分发,每个类型单独计算,最后合并
(U extends U ? (x: U) => unknown : never)
获得交叉类型
TS 中有函数参数是有逆变性,如果参数是多个类型,参数类型会变成它们的交叉类型
? R
可以改为 ? { [K in keyof R]: R[K] }
这样可以归一化平铺ab属性
以后要使用归一化要记住,是对谁进行归一化,就对谁直接这样替换
8. GetOptional
提取索引类型中的可选索引
可选属性其Key可能没有,即{}
是其子类型
type GetOptional<Obj extends Record<string, any>> = {
[Key in keyof Obj as {} extends Pick<Obj, Key>
? Key
: never]: Obj[Key]
}
// { b?: number | undefined; }
type testGetOptional = GetOptional<{ a: 1; b?: number }>
可选索引 表现为值是 T | undefined
, 但 T | undefined
不代表是可选索引
可选索引 必须有 ?
9. GetRequired
与可选索引相反的就是required
type isRequired<
Key extends keyof Obj,
Obj
> = {} extends Pick<Obj, Key> ? never : Key
type GetRequired<Obj extends Record<string, any>> = {
[Key in keyof Obj as isRequired<Key, Obj>]: Obj[Key]
}
// { a: 1 }
type testGetRequired = GetRequired<{ a: 1; b?: number }>
10. RemoveIndexSignature
{[key: string]: any}
索引签名, 代表可添加任意 Key 为 string 索引
// 删除 索引签名
type RemoveIndexSignature<Obj extends Record<string, any>> = {
[Key in keyof Obj as Key extends `${infer Str}`
? Str
: never] : Obj[Key]
}
// { a: 1 }
type testGetRemoveIndexSignature = RemoveIndexSignature<{
[key: string]: any
a: 1
}>
索引签名不能构造成字符串字面量类型,因为它没有名字,而其他索引可以
11. ClassPublicProps
过滤出 class 的 public属性
keyof 只能拿到 class 的 public 索引,不能拿到 private 和 protected
type ClassPublicProps<Obj extends Record<string, any>> = {
[Key in keyof Obj]: Obj[Key]
}
class testClass {
public a: boolean
protected b: number
private c: string
}
// { a: boolean }
type testGetClassPublicProps = ClassPublicProps<testClass>
12. as const
TS 默认推导出来的类型不会是字面量类型,也就是某一固定值
const obj = { a:1,b:2 }
// { a: number; b: number; }
type TypeObj = typeof obj
const obj2 = { a:1,b:2 } as const
// { readonly a: 1; readonly b: 2; }
type TypeObj = typeof obj2
as const
具有常量 和 readonly 双重含义
所以通过typeof (常量 as const)
推导出 的字面量类型必含 readonly属性
所以再通过 模式匹配提取类型 时也要加上 readonly 的修饰才行
const arrConst = [1, 2] as const
type arrConstType = typeof arrConst
type IsConstOnly<Arr> =
// 这里必须加 readonly
Arr extends readonly [infer A, infer B] ? true : false
// true
type testGetConstOnly = IsConstOnly<arrConstType>
as const
常见使用场景, 模拟枚举
const colors = ['red', 'green', 'blue'] as const;
// "red" | "green" | "blue"
type Color = typeof colors[number];
13.