ts中的Pick、Omit、Extract和Exclude

今天學(xué)習(xí)typescript時對Pick、Omit、Extract和Exclude這四個方法的使用產(chǎn)生了困惑,特此記錄。我認為可以整體分為兩部分Pick和Omit、Extract和Exclude

由于Pick和Omit的實現(xiàn)依賴于Exclude因此我們先介紹下Extract和Exclude

Extract和Exclude

Extract<Type, Union>

提取Type中所有能夠賦值給Union的屬性,將這些屬性構(gòu)成一個新的類型
Constructs a type by extracting from Type all union members that are assignable to Union.

官方例子https://www.typescriptlang.org/docs/handbook/utility-types.html#extracttype-union

type T0 = Extract<"a" | "b" | "c", "a" | "f">;

因為a assignable to a|f,其他的都不行,所以T0='a'

Exclude<UnionType, ExcludedMembers>

UnionType中去掉所有能夠賦值給ExcludedMembers的屬性,然后剩下的屬性構(gòu)成一個新的類型
Constructs a type by excluding from UnionType all union members that are assignable to ExcludedMembers.

官方例子https://www.typescriptlang.org/docs/handbook/utility-types.html#excludeuniontype-excludedmembers

type T0 = Exclude<"a" | "b" | "c", "a">;

因為a assignable to a,被去掉了,所以T0='b'|'c'
看到這里可能覺得沒什么難的,我們再看下兩者的源碼

type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;

extends用法可以看https://juejin.cn/post/6998736350841143326

  • T extends U可以理解為 T是否assignable到U
    • 如果assignable,對于Exclude就得到never,因為匹配上的就要去掉
    • 同樣的,Extract就保留

那么問題來了?看著源碼理解的話
type T0 = Extract<"a" | "b" | "c", "a" | "f">;應(yīng)該得到T0 = "a" | "b" | "c"才對,是一個一榮俱榮,一損俱損的狀態(tài),a是怎么被單獨Extract的呢?
其實答案在extends中,extends為我們執(zhí)行了分配律

type T0 = Extract<"a" | "b" | "c", "a" | "f">
            = “a” extends "a"|"f" ? never : "a" |  //"a"
              “b” extends "a"|"f" ? never : "b" |  //never 
              “c” extends "a"|"f" ? never : "c" //never
            = "a"

同理對于Exclude

type T0 = Exclude<"a" | "b" | "c", "a" | "f">
            = “a” extends "a"|"f" ? never : "a" | //never
              “b” extends "a"|"f" ? never : "b" |   //"b"
              “c” extends "a"|"f" ? never : "c"     //"c"
            = "b"|"c"

那么仔細看文檔的同學(xué)會發(fā)現(xiàn)文檔中的參數(shù)很多都帶著Union的前綴,為什么要一直強調(diào)Union呢?如果我們很調(diào)皮非要用單個type中選擇/排除部分屬性時該怎么辦呢?可以用Pick或Omit。Extract和Exclude的設(shè)計就是讓我們從聯(lián)合類型中選擇有用的。

type Test = {
 name: string;
 age: number;
 salary?: number;
};
//無效,這樣沒有意義,并不能夠刪除其中的字段
type wrongExcluded = Exclude<Test, "salary">;
type salary = { salary?: number };
//有效
type excluded1 = Exclude<Test, salary>; //never,
//有效且有意義
type excluded2 = Exclude<Test | salary | { noSalary: boolean }, salary>; //{ noSalary: boolean }

Pick和Omit

Pick<Type, Keys>

從Type中選取一系列的屬性,這些屬性來自于Keys(字符串字面量或字符串字面量的聯(lián)合類型),用這些屬性構(gòu)成新的type。
Constructs a type by picking the set of properties Keys (string literal or union of string literals) from Type.

type Test = {
  name: string;
  age: number;
  salary?: number;
};

//pick
type picked = Pick<Test, "name" | "age">;
// 結(jié)果
// type picked = {
//     name: string;
//     age: number;
// }

這個其實很好理解,因為是Keys是name|age的聯(lián)合類型,所以從Test中nameage被挑選了出來
如果Keys在Type中不存在呢?
ts會報錯

Test中不存在a屬性

再看一下Pick的實現(xiàn),這就很好理解了。extends限制了K值必須屬于Type的屬性值(keyof Type)

type Pick<Type, K extends keyof Type> = { [P in K]: Type[P]; }

Omit<Type, Keys>

從Type中選取所有的屬性值,然后移除屬性名在Keys中的屬性值
Constructs a type by picking all properties from Type and then removing Keys (string literal or union of string literals).

本質(zhì)上是Pick的反向操作,排除掉Keys。
Omit相對而言復(fù)雜一些

type Test = {
  name: string;
  age: number;
  salary?: number;
};
type omitted = Omit<Test, "age">;
// 結(jié)果
// type omitted = {
//     name: string;
//     salary?: number;
// }

看著例子覺得沒什么,但是事情沒這么簡單
可以猜猜下面的例子是什么答案

type omitted = Omit<"a" | "b", "a">;

如果猜不出可以猜猜對應(yīng)的Picked是什么答案

type picked = Picked<"a" | "b", "a">;

看下答案:
ommitted 很多屬性 但是看著像不像字符串上的方法呢?


omit

picked報錯,原因很簡單,a不屬于keyof 'a'|'b'

picked

那么為什么omit不報錯呢?我們看看源碼

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
  • K extends any你寫啥都不報錯
    然后我們逐步拆解下Omit的每一步做了什么
type T = "a" | "b";
type omitted = Omit<T, "a">;
//最關(guān)鍵
type keyofString = keyof T;
type excludedKeyOfString = Exclude<keyofString, "a">;
type final = {
  [P in excludedKeyOfString]: T[P];
};

最關(guān)鍵的步驟是這一行
type keyofString = keyof T這一步我們得到了一個聯(lián)合類型,類型大致如下

keyof 'a'

所以上面說的不止看著像,實際上就是一個字符串的屬性和方法。
那字符串方法上有沒有a屬性呢?顯然沒有。所以Exclude<keyof T, K>沒用。
最后,Pick<T, Exclude<keyof T, K>>,T="a"|"b"自然具有字符串的所有屬性,也因此Pick把所有的屬性都選了出來,Pick了個寂寞。

文章寫到這里就結(jié)束啦,小小分享,歡迎提問

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容