特殊特性
引入
TypeScript 类型系统中有些类型比较特殊,比如 any、never、联合类型,比如 class 有 public、protected、private 的属性,比如索引类型有具体的索引和可索引签名,索引还有可选和非可选。。。
类型的判断要根据它的特性来,比如判断联合类型就要根据它的 distributive
的特性
特殊类型的特性
IsAny
注
any 类型与任何类型的交叉都是 any,也就是 1 & any 结果是 any
type IsAny<T> = 0 extends 1 & T ? true : false;
IsEqual
之前写的 IsEqual
是不能 处理 any
的
type IsEqualPlus<A, B> = (<T>() => T extends A ? 1 : 2) extends <
T,
>() => T extends B ? 1 : 2
? true
: false;
相关信息
TS 对这种形式的类型做了特殊处理,是一种 hack 的写法
IsUnion
type IsUnion<A, B = A> = A extends A ? ([B] extends [A] ? false : true) : never;
IsNever
never 在条件类型中也比较特殊,如果条件类型左边是类型参数
,并且传入的是 never,那么直接返回 never
type IsNever<T> = [T] extends [never] ? true : false;
提示
any 在条件类型中也比较特殊,如果类型参数为 any,会直接返回 trueType 和 falseType 的合并
type TestAny<T> = T extends number ? 1 : 2;
IsTuple
注
元组类型的 length 是数字字面量
,而数组的 length 是 number
type NotEqual<A, B> = (<T>() => T extends A ? 1 : 2) extends <
T,
>() => T extends B ? 1 : 2
? false
: true;
type IsTuple<T> = T extends [...params: infer Eles]
? NotEqual<Eles['length'], number>
: false;
提示
IsTuple
类型参数 T 是要判断的类型。
首先判断 T 是否是数组类型,如果不是则返回 false。如果是继续判断 length 属性是否是 number。
如果是数组并且 length 不是 number 类型,那就代表 T 是元组
NotEqual
A 是 B 类型,并且 B 也是 A 类型,那么就是同一个类型,返回 false,否则返回 true
UnionToIntersection
注
类型之间是有父子关系的,更具体的那个是子类型, A & B
就是 A | B
的子类型,因为更具体。
如果允许父类型赋值给子类型,就叫做逆变
如果允许子类型赋值给父类型,就叫做协变
在 TypeScript 中有函数参数是有逆变
的性质的,也就是如果参数可能是多个类型,参数类型会变成它们的交叉类型
type UnionToIntersection<U> = (
U extends U ? (x: U) => unknown : never
) extends (x: infer R) => unknown
? R
: never;
提示
类型参数 U 是要转换的联合类型。
U extends U 是为了触发联合类型的 distributive 的性质,让每个类型单独传入做计算,最后合并。
利用 U 做为参数构造个函数,通过模式匹配取参数的类型
GetOptional
type GetOptional<Obj extends Record<string, any>> = {
[Key in keyof Obj as {} extends Pick<Obj, Key> ? Key : never]: Obj[Key];
};
思路
怎么去掉不是 可选
的 key
呢, as
重构 索引类型 并变成 never
怎么去筛选 是否是 包含 可选
, 我们 用 Pick
重新构造一个 对象
然后怎么去判断 这个对象就是我们要做的, 看下面例子
// 如果 age 不是可选, t 是 false, 否则是 true
type t = {} extends { age: number } ? true : false;
GetRequired
思路 和 GetOptional
as 那 相反
type GetOptional<Obj extends Record<string, any>> = {
[Key in keyof Obj as {} extends Pick<Obj, Key> ? never : Key]: Obj[Key];
};
RemoveIndexSignature
索引类型可能有索引,也可能有可索引签名
type Dog = {
[key: string]: any; // 可索引签名
sleep(): void; // 具体的索引
};
type RemoveIndexSignature<Obj extends Record<string, any>> = {
[Key in keyof Obj as Key extends `${infer Str}` ? Str : never]: Obj[Key];
};
提示
如果索引是字符串字面量类型,那么就保留,否则返回 never,代表过滤掉
ClassPublicProps
注
keyof 只能拿到 class
的 public
索引,private 和 protected 的索引会被忽略。
class Dog {
public name: string;
protected age: number;
private hobbies: string[];
constructor() {
this.name = 'zz';
this.age = 2;
this.hobbies = ['sleep', 'eat'];
}
}
type ClassPublicProps<Obj extends Record<string, any>> = {
[Key in keyof Obj]: Obj[Key];
};
as const
TypeScript 默认推导出来的类型并不是字面量类型
注
加上 as const
之后推导出来的类型是带有 readonly
修饰的,所以再通过模式匹配提取类型的时候也要加上 readonly
的修饰才行
const obj = {
a: 1,
b: 2,
};
type Obj = typeof obj;
const objC = {
a: 1,
b: 2,
} as const;
type ObjC = typeof objC;
const arr = [1, 2, 3] as const;
type Arr = typeof arr;
总结
- any 类型与任何类型的交叉都是 any,也就是 1 & any 结果是 any,可以用这个特性判断 any 类型
- 联合类型作为类型参数出现在条件类型左侧时,会分散成单个类型传入,最后合并
- never 作为类型参数出现在条件类型左侧时,会直接返回 never
- any 作为类型参数出现在条件类型左侧时,会直接返回 trueType 和 falseType 的联合类型
- 元组类型也是数组类型,但 length 是数字字面量,而数组的 length 是 number。可以用来判断元组类型
- 函数参数处会发生逆变,可以用来实现联合类型转交叉类型
- 可选索引的索引可能没有,那 Pick 出来的就可能是 {},可以用来过滤可选索引,反过来也可以过滤非可选索引
- 索引类型的索引为字符串字面量类型,而可索引签名不是,可以用这个特性过滤掉可索引签名
- keyof 只能拿到 class 的 public 的索引,可以用来过滤出 public 的属性
- 默认推导出来的不是字面量类型,加上 as const 可以推导出字面量类型,但带有 readonly 修饰,这样模式匹配的时候也得加上 readonly 才行