typescript:never與keyof的妙用

Otocolobus-manul發表於2019-04-19

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做一個索引型別,把物件型別變成聯合型別,就得到了我們想要的那些成員名稱。

相關文章