ts中的Pick、Omit、Extract和Exclude

今天学习typescript时对Pick、Omit、Extract和Exclude这四个方法的使用产生了困惑,特此记录。我认为可以整体分为两部分Pick和OmitExtract和Exclude

由于Pick和Omit的实现依赖于Exclude因此我们先介绍下Extract和Exclude

Extract和Exclude

Extract<Type, Union>

提取Type中所有能够赋值给Union的属性,将这些属性构成一个新的类型
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的属性,然后剩下的属性构成一个新的类型
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">;应该得到T0 = "a" | "b" | "c"才对,是一个一荣俱荣,一损俱损的状态,a是怎么被单独Extract的呢?
其实答案在extends中,extends为我们执行了分配律

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"

那么仔细看文档的同学会发现文档中的参数很多都带着Union的前缀,为什么要一直强调Union呢?如果我们很调皮非要用单个type中选择/排除部分属性时该怎么办呢?可以用Pick或Omit。Extract和Exclude的设计就是让我们从联合类型中选择有用的。

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(字符串字面量或字符串字面量的联合类型),用这些属性构成新的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">;
// 结果
// type picked = {
//     name: string;
//     age: number;
// }

这个其实很好理解,因为是Keys是name|age的联合类型,所以从Test中nameage被挑选了出来
如果Keys在Type中不存在呢?
ts会报错

Test中不存在a属性

再看一下Pick的实现,这就很好理解了。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).

本质上是Pick的反向操作,排除掉Keys。
Omit相对而言复杂一些

type Test = {
  name: string;
  age: number;
  salary?: number;
};
type omitted = Omit<Test, "age">;
// 结果
// type omitted = {
//     name: string;
//     salary?: number;
// }

看着例子觉得没什么,但是事情没这么简单
可以猜猜下面的例子是什么答案

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

如果猜不出可以猜猜对应的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">;
//最关键
type keyofString = keyof T;
type excludedKeyOfString = Exclude<keyofString, "a">;
type final = {
  [P in excludedKeyOfString]: T[P];
};

最关键的步骤是这一行
type keyofString = keyof T这一步我们得到了一个联合类型,类型大致如下

keyof 'a'

所以上面说的不止看着像,实际上就是一个字符串的属性和方法。
那字符串方法上有没有a属性呢?显然没有。所以Exclude<keyof T, K>没用。
最后,Pick<T, Exclude<keyof T, K>>,T="a"|"b"自然具有字符串的所有属性,也因此Pick把所有的属性都选了出来,Pick了个寂寞。

文章写到这里就结束啦,小小分享,欢迎提问

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354