今天學(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 fromTypeall union members that are assignable toUnion.
官方例子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 fromUnionTypeall union members that are assignable toExcludedMembers.
官方例子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中name和age被挑選了出來
如果Keys在Type中不存在呢?
ts會報錯

再看一下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 很多屬性 但是看著像不像字符串上的方法呢?

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

那么為什么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)合類型,類型大致如下

所以上面說的不止看著像,實際上就是一個字符串的屬性和方法。
那字符串方法上有沒有
a屬性呢?顯然沒有。所以Exclude<keyof T, K>沒用。最后,
Pick<T, Exclude<keyof T, K>>,T="a"|"b"自然具有字符串的所有屬性,也因此Pick把所有的屬性都選了出來,Pick了個寂寞。
文章寫到這里就結(jié)束啦,小小分享,歡迎提問