今天学习typescript时对Pick、Omit、Extract和Exclude这四个方法的使用产生了困惑,特此记录。我认为可以整体分为两部分Pick和Omit、Extract和Exclude
由于Pick和Omit的实现依赖于Exclude因此我们先介绍下Extract和Exclude
Extract和Exclude
Extract
提取Type中所有能够赋值给Union的属性,将这些属性构成一个新的类型
Constructs a type by extracting fromType
all 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的属性,然后剩下的属性构成一个新的类型
Constructs a type by excluding fromUnionType
all 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 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中name
和age
被挑选了出来
如果Keys在Type中不存在呢?
ts会报错
再看一下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 很多属性 但是看着像不像字符串上的方法呢?
picked报错,原因很简单,a
不属于keyof 'a'|'b'
那么为什么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
这一步我们得到了一个联合类型,类型大致如下
所以上面说的不止看着像,实际上就是一个字符串的属性和方法。
那字符串方法上有没有
a
属性呢?显然没有。所以Exclude
没用。
最后,
Pick>
,T="a"|"b"
自然具有字符串的所有属性,也因此Pick把所有的属性都选了出来,Pick了个寂寞。
文章写到这里就结束啦,小小分享,欢迎提问