typescript:never与keyof的妙用

5,462 阅读3分钟

never类型

typescript的never类型代表永不存在的值的类型,它只能被赋值为never

任意类型与never交叉都得到never

type T1 = number & never;   // never
type T2 = string & never;   // never

可以这样理解:若type T = T1 & T2,则T类型的值可以赋给T1T2类型的变量(类似类的继承关系)。 那么若与never交叉,则T类型的值可以赋给一个never类型的变量,那T只能是never了。

任意类型与never联合不受影响:

type T1 = number | never;   // number
type T2 = string | never;   // string

理解与上述交叉类型情况类似: 若type T = T1 | T2,则T1T2类型的值可以赋给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成员的名称找出,从Tpick出来。所以先弄一个对象类型出来:

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做一个索引类型,把对象类型变成联合类型,就得到了我们想要的那些成员名称。