ts中的Pick、Omit、Extract和Exclude

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

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

Extract和Exclude

Extract

提取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的属性,然后剩下的属性构成一个新的类型
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 extends U ? never : T;
type Extract = 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;
type salary = { salary?: number };
//有效
type excluded1 = Exclude; //never,
//有效且有意义
type excluded2 = Exclude; //{ noSalary: boolean }

Pick和Omit

Pick

从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;
// 结果
// type picked = {
//     name: string;
//     age: number;
// }

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

Test中不存在a属性

再看一下Pick的实现,这就很好理解了。extends限制了K值必须属于Type的属性值(keyof Type)

type Pick = { [P in K]: Type[P]; }

Omit

从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;
// 结果
// 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 = Pick>;
  • K extends any你写啥都不报错
    然后我们逐步拆解下Omit的每一步做了什么
type T = "a" | "b";
type omitted = Omit;
//最关键
type keyofString = keyof T;
type excludedKeyOfString = Exclude;
type final = {
  [P in excludedKeyOfString]: T[P];
};

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

keyof 'a'

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

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

你可能感兴趣的:(ts中的Pick、Omit、Extract和Exclude)