never类型
typescript的never
类型代表永不存在的值的类型,它只能被赋值为never
。
任意类型与never
交叉都得到never
:
type T1 = number & never; // never
type T2 = string & never; // never
可以这样理解:若type T = T1 & T2
,则T
类型的值可以赋给T1
或T2
类型的变量(类似类的继承关系)。
那么若与never
交叉,则T
类型的值可以赋给一个never
类型的变量,那T
只能是never
了。
任意类型与never
联合不受影响:
type T1 = number | never; // number
type T2 = string | never; // string
理解与上述交叉类型情况类似:
若type T = T1 | T2
,则T1
或T2
类型的值可以赋给T
类型的变量。
由于never
类型可以赋给任意变量,自然对联合类型不产生影响了。
keyof
typescript的keyof
关键字,将一个类型映射为它所有成员名称的联合类型。如typescript官网上的示例:
interface Person {
name: string;
age: number;
location: string;
}
type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[]; // "length" | "push" | "pop" | "concat" | ...
type K3 = keyof { [x: string]: Person }; // string
keyof实际给我们一个操作联合类型的途径。结合typescript的其他feature,如类型映射与索引类型,我们得以在对象类型与联合类型之间游刃有余地转换,为工程中更多变量找到最适合的类型归属。
应用
Diff Type
我们看一个来自这里的例子:
type Diff<T extends string, U extends string> = ({ [P in T]: P } & { [P in U]: never })[T];
type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>;
下面一行不用过多解释了,在T类型中除去成员名在K中的成员。而上面一行代码特别有意思,也特别难看懂,它所做的是对于T与U两个字符串字面量类型,从T中除去包含在U中的那些字符串:
type A = Diff<"a" | "b" | "c", "a">; // "b" | "c"
type B = Diff<"a" | "b" | "c", "b" | "d">; // "a" | "c"
它是如何做到的呢?我们首先看它的前面部分:
type FirstHalf<T extends string, U extends string> = { [P in T]: P } & { [P in U]: never }
type C = FirstHalf<"a" | "b" | "c", "b" | "d">;
// {
// "a": "a",
// "b": "b",
// "c": "c"
// } & {
// "b": never,
// "d": never
// }
我们再将type C
做逐成员的交叉:
type C = {
"a": "a",
"b": "b" & never,
"c": "c",
"d": never
}
任意类型与never
交叉的结果都是never
,因此
type C = {
"a": "a",
"b": never,
"c": "c",
"d": never
}
我们再看Diff
类型:
type B = Diff<"a" | "b" | "c", "b" | "d">;
= {
"a": "a",
"b": never,
"c": "c",
"d": never
}["a" | "b" | "c"];
= "a" | never | "c";
= "a" | "c";
这样就达到了前文所述的目的。
去除所有never成员
我们试图移除一个object type
中所有类型为never
的成员。可以这样操作:
type OmitNever<T> = Pick<T, {[P in keyof T]: T[P] extends never ? never : P}[keyof T]>;
type T = {
a: string,
b: never,
c: string,
}
type T1 = OmitNever<T>; // { a: string, c: string }
原理类似第一个例子。我们试图把T
中所有非never
成员的名称找出,从T
中pick
出来。所以先弄一个对象类型出来:
type OmitNeverHalf<T> = {[P in keyof T]: T[P] extends never ? never : P}
type TT = OmitNeverHalf<T>;
// {
// "a": "a",
// "b": never,
// "c": "c"
// }
再用keyof T
做一个索引类型,把对象类型变成联合类型,就得到了我们想要的那些成员名称。