这是我们今天要聊的内容,老样的,如果你已经可以轻松看懂,欢迎直接左转去看同事 Andy 精彩的文章 — 「前端工程师学习 DevOps 之路」。
在学习了 Mapped Types 後,我们已经把多数要用来操作型别所需的知识补齐了,後面几天就来看一些实际会用到的实作,包括 TypeScript 中内建以及其他第三方提供的 Utility Types。
今天让我们先来看 Pick
和 Record
。
在 TypeScript 内建的 Utility Types 中,有一个 Pick<T, K extends keyof T>
,使用方式很简单,它可以帮选择要保留下物件型别中的那些属性,例如:
type Person = {
firstName: string;
lastName: string;
age: number;
};
type PersonName = Pick<Person, 'firstName' | 'lastName'>;
而 PersonName
的型别就会等同於这样:
type PersonName = {
firstName: string;
lastName: string;
};
在有了前面关於型别操作的知识後,会发现 Pick
的原始码其实也蛮单纯好理解的:
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
我们可以看到 Pick
吃两个型别参数,一个是 T
它会是物件型别,後面的 K
因为有用的 Day03 提的泛型限制,所以 K
一点要满足是物件型别 T
中有的属性 key。
接着 [P in K]
很明显的是 Mapped Types,以上面提的 PersonName
为例,K
就会是 'firstName' | 'lastName'
,这时候的 [P in 'firstName' | 'lastName']
读者们应该可以预想到最後出来的型别其物件型别的属性 key 会长这样:
{
firstName: '...';
lastName: '...';
}
最後看到属性 value 的部分是 T[P]
,意思也就是,什麽都不做,原本物件属性值的型别是什麽就直接拿来用,因此最後 PersonName
的型别会长这样:
type PersonName = {
firstName: string;
lastName: string;
};
接着我们来看一下 Record<Keys, Type>
。前面我们有提过 Mapped Types 是比较有限制的 Index Signatures,也就是 Mapped Types 是 Index Signatures 的子集合,而 Record
这个 Utility Type 同样是基於 Mapped Types 写出来的,先来简单看一下它的用法。
假设现在需要建立一个物件型别,它的 key 希望符合 ConferenceName
、value 符合型别 Conference
:
type ConferenceName = 'ModernWeb' | 'MOPCON' | 'JSDOC' | '{Laravel x Vue}';
type Conference = {
name: string;
year: number;
isAddToCalendar: boolean;
website: string;
};
前面 Day14 曾提过,因为 Index Signatures 的特性,并没有办法直接写:
type ConferenceIndexSignatures = {
[K: ConferenceName]: Conference;
};
TypeScript 会报错,并建议我们用 Mapped Type:
於是用 Mapped Types 可以写成这样:
type ConferenceMap = {
[P in ConferenceName]: Conference;
};
接着我们把它改成更泛用的形式试试看,先把 ConferenceName
抽成泛型:
// 把 ConferenceName 变成泛型
type ToConferenceMap<K> = {
[P in K]: Conference;
};
这时候你会看到 TypeScript 报错:
之所以会有这个错误的原因是 K
没有办法被保证一定能把叠代,如果 K 被带入物件型别的话,[P in K]
就会坏掉,因此 TypeScript 说 K
应该只能是 string | number | symbol
,而这其实也就是 keyof any
的意思,因此可以透过泛型限制来限制使用者可以带入的 K
:
type ToConferenceMap<K extends keyof any> = {
[P in K]: Conference;
};
接着我们来把物件型别的属性值也抽成泛型:
type ToConferenceMap<K extends keyof any, T> = {
[P in K]: T;
};
有没有发现刚刚写的 ToConferenceMap
变的更泛用了!现在我们可以使用刚刚自己写的这个 Utility Type 来产生 ConferenceMap
:
type ConferenceName = 'ModernWeb' | 'MOPCON' | 'JSDC' | '{Laravel x Vue}';
type Conference = {
name: string;
year: number;
isAddToCalendar: boolean;
website: string;
};
type ToConferenceMap<K extends keyof any, T> = {
[P in K]: T;
};
type ConferenceMap = ToConferenceMap<ConferenceName, Conference>;
如此就可以得到我们想要的物件型别:
更重要的是,其实我们已经写出了官方提供的 Record
了!
在 TypeScript 中,Record<Keys, Type>
可以让开发者方便定义物件型别中属性 key 和 value 的型别,而你会发现用 Record 建立出来的型别,和用刚刚我们写的 ToConferenceMap
的效果一样:
// 两个建立出来的型别是一样的
type ConferenceMap = ToConferenceMap<ConferenceName, Conference>;
type ConferenceRecord = Record<ConferenceName, Conference>;
回过头来看 Record
的原始码:
// Construct a type with a set of properties K of type T
type Record<K extends keyof any, T> = {
[P in K]: T;
};
有没有发现和我们刚刚写的 ToConferenceMap
其实是一样的呢?
读者们应该慢慢可以发现,要写出 Utility Types 比想像中的简单,可以先根据实际的情况撰写出想要的结果後,再把可以抽成参数的部分变成泛型,最後就可以建立出更泛用的 Utility Types。
当然也是有一些很复杂的 Utility Types 可能没办法这麽简单就写的出来,但重要的是能够透过正确的断句,先求能够看懂和理解。
https://tsplay.dev/WJ8E5N @ TypeScript Playground
Scale() - 缩放 相对於目前的画布大小进行缩放,如 scale(0.5)。 -> 变成...
今天要说明的是Grafana部署的部分。依照在Day 23 中的软件架构图在云端与边缘端各自布署了一...
Kibana是一套分析和视觉化的软件,可以快速的帮助使用者更好的应用和分析资料。在接下来我们要开始介...
Motion Editor是自 Android Studio 4.0 版本开始为MotionLayo...
利用布林值来决定如何继续执行程序进行决策 例 let n = 3; if n > 2 { pr...