今天这个范例是来自第三方套件 utility-types,在有了前几天的知识後,让我们来试着了解这个 Utility Type 是如何实作的吧!如果你已经可以轻松看懂,欢迎直接左转去看我队友们的精彩文章!
要把物件型别中的属性全部变成 Optional 的话,在 Day16 时曾经提过可以使用官方内建的 Partial
,但假设今天只想要让这个物件型别中的部分属性变成 Optional 的话,就可以用这里的这个 Optional<T, K>
。
备注:这里的 Optional 并不是 TypeScript 中内建的 Utility Type,读者如果要使用的话,记得要先复制这个 Utility Type 的原始码到程序码中。
举例来说,现在有一个型别 Conference
:
type Conference = {
name: string;
year: number;
isAddToCalendar: boolean;
website: string;
};
後来发现 Conference
这个型别中,year
和 isAddToCalendar
都可以省略(Optional),只有名称和网址是必填的,这时候就可以用 Optional 来达到:
type ConferenceWithOptional = Optional<Conference, 'year' | 'isAddToCalendar'>;
这时候这个 ConferenceWithOptional
就会变成是:
// year 和 isAddToCalendar 变成 optional 的
type ConferenceWithOptional = {
name: string;
year?: number;
isAddToCalendar?: boolean;
website: string;
};
但这个 Optional 还有一个蛮特别的地方,它也可以只带入物件型别给它就好,而不告诉它那些属性 Key 是要变成 optional 的:
// 没有带入 Optional 的第二个参数
type ConferenceWithAllOptional = Optional<Conference>;
这时候 ConferenceWithAllOptional
它「预设」就会把该物件型别中的所有 Key 都变成 Optional 的了:
// 预设会把所有属性都变 Optional
type ConferenceWithAllOptional = {
name?: string;
year?: number;
isAddToCalendar?: boolean;
website?: string;
};
这里我们发现两个重要的点:
很特别吧!让我们来理解看看它是怎麽被实作的吧!
Optional
这个 Utility Type 的原始码如下:
type Optional<T extends object, K extends keyof T = keyof T> = Omit<T, K> &
Partial<Pick<T, K>>;
要了解原始码,最重要的就是要知道如何做出正确的断句,让我们先把注意力放到 =
的前面:
没错,这一长串都是 =
前面,从 <T extends ...>
开始,可以理解到 Optional
它吃两个参数 T
和 K
:
在 Day03 中我们提过这是属於泛型限制的写法,所以 T extends object
就是 T
需要是 object
的子集合;後面的 K extends keyof T = keyof T
好像出现了我们不曾看过的语法,没错!这就是今天的重点「泛型参数预设值(Generic parameter defaults)」。
泛型参数预设值的用法就和 JavaScript 函式中带入参数预设值的方式一样,都是用等号(=
),在知道泛型的参数也能带入预设值之後,回过头来看刚刚的 K extends keyof T = keyof T
:
K extends keyof T
的意思是: K
需要满足 keyof T
,也就是说,K
需要是 T
这个物件型别中所包含的属性 key。K ... = keyof T
的意思就是,如果没给 K
的话,预设就让 K
的型别等同於 keyof T
,也就是预设的 K
会是所有物件型别中的所有 key。备注:泛型参数预设值并没有一定要搭配泛型限制(extends)使用。
接着把重点放到 =
的後面:
虽然前几天曾提过 Omit
、Partial
和 Pick
的用法,但可能还是会忘,这时候把握前面提过的原则:「不确定时就带入实际的型别试试看」:
type A = Omit<Conference, 'year' | 'isAddToCalendar'>;
type B = Pick<Conference, 'year' | 'isAddToCalendar'>;
type C = Partial<Pick<Conference, 'year' | 'isAddToCalendar'>>;
你会发现 A
其实就是把要变成 Optional 的属性「忽略」掉,只留下不改变的部分:
而 B
就是把要变成 Optional 的属性「挑出」来:
最後的 C
只是把 B
当成参数带入 Partial
中,让这些物件属性全都变 optional 的。所以最後的 Result
就会是「没被挑到的什麽都不做(A
)」加上「被挑到的都变成 Optional(C
)」:
type A = {
name: string;
website: string;
};
type C = {
year?: number | undefined;
isAddToCalendar?: boolean | undefined;
};
type Result = A & C;
Result
就会是:
type Result = {
name: string;
website: string;
year?: number | undefined;
isAddToCalendar?: boolean | undefined;
};
透过带入实际型别的方式,回过头来看 Optional
这个 Utility Type 的回传值 Omit<T, K> & Partial<Pick<T, K>>
就更能够理解它的意思。
最後,你可能会好奇,为什麽这里需要帮型别加上预设值呢?「当你不清楚为什麽要多这个的时候,最好的方式就是把它拿掉试试看会发生什麽事」,因此如果我们把 Optional
原始码中的泛型参数预设值拿掉(即,删掉= keyof T
的部分),改成:
结果会发现,原本我们只带入一个参数用法的地方跳出错误了,因为和 JavaScript 的函式一样,在没给参数预设值的情况下,每个参数都需要带好带满才行:
https://tsplay.dev/Nlp75N @ TypeScript Playground
<<: D19 - 彭彭的课程# Python 文字档案的读取和储存(1)
>>: IT 铁人赛 k8s 入门30天 -- day20 k8s Logging Architecture
本系列文之後也会置於个人网站 +--------+ +---------------+ | |--...
继昨天讲了的ConstraintLayout,今天要来介绍自己也常用的另外两个布局,LinearLa...
-使用私钥和公钥对强大的程序集进行签名和验证 (来源:https://flylib.com/boo...
这边要强调一下,我相当珍惜公司给予的居家上班机会, 完全没有想过或做过以下行为,只是以少数人情况举例...
至目前的章节为止,已经可以执行 Genero FGL的程序在 Windows/MAC/Linux ...