持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情
前言
在学习typescript的过程当中,有一个github库对其类型的学习特别有帮助,是一个有点类似于leetcode的刷题项目,能够在里面刷各种关于typescript类型的题目,在上一篇文章中,我们完成了中等的前两题,今天来做中等的第三题 8-medium-readonly-2
下面这个是类型体操github仓库:
8-medium-readonly-2
今天的题目需要我们实现进阶版本的 Readonly
import type { Alike, Expect } from '@type-challenges/utils'
type cases = [
Expect<Alike<MyReadonly2<Todo1>, Readonly<Todo1>>>,
Expect<Alike<MyReadonly2<Todo1, 'title' | 'description'>, Expected>>,
Expect<Alike<MyReadonly2<Todo2, 'title' | 'description'>, Expected>>,
]
interface Todo1 {
title: string
description?: string
completed: boolean
}
interface Todo2 {
readonly title: string
description?: string
completed: boolean
}
interface Expected {
readonly title: string
readonly description?: string
completed: boolean
}
TS 存在着内置的工具类型 Readonly 能够将所有的对象属性都转为只读,题目需要我们提供一个升级版本,根据传入的key来进行只读转化,没有传入key的话就和Readonly作用是相同的。
利用JS进行对比学习
我们使用js来模拟一下题目需要的过程,传入一个对象和一个联合类型(这里用数组来模拟联合类型),将数组存在的键值转为只读,也就是在对象属性前面加上 readonly。
并且 数组 是一个可选参数,未传入的时候,就将所有的键值都变为 readonly。
function ReadonlyByKey(Obj:any,keys?:string[]){
const MyReadonly:any = {};
if(keys && keys.length != 0){
for(let key in Object.keys(Obj)){
if(!keys.includes(key)){
MyReadonly["readonly" + key] = Obj[key];
}
}
}else {
for (let key in Object.keys(Obj)) {
MyReadonly["readonly" + key] = Obj[key];
}
}
return MyReadonly
}
上面的代码中我们就分为两种情况,有传入和未传入的不同执行逻辑。
实现 MyReadonly2
首先根据上面的 JS 分析,我们能够得到,其实这道题需要分为两部分,一种是 K 传入的情况,一种是未传入的情况,那么我们首先先来看一下传入的情况,就是我们比较熟悉的,根据指定的 Key 值,来改变对象的可选或者只读
type MyReadonly2<T, K extends keyof T> = { readonly [P in K]: T[P] };
type ASD = MyReadonly2<Todo3, 'title' | 'description'>
// type ASD = {
// readonly title: string;
// readonly description?: string | undefined;
// }
这样我们就能够改变 K 对应的 key 值 为readonly,但是会发现,现在剩下的就只有 K 里面的属性了,那么我们可以利用交叉类型和 Omit 进行配合,将剩下的属性合并进去,这里为什么会需要 Omit,因为我们需要排除掉被修改过的 key 值,不然会覆盖掉之前的修改,可以看下面这段代码:
type MyReadonly2<T, K extends keyof T> = {
readonly [P in K]: T[P];
} & T;
type ASD = MyReadonly2<Todo3, "title" | "description">;
type SAS = {
[P in keyof ASD]: ASD[P];
};
// type SAS = {
// readonly title: string;
// description?: string | undefined;
// completed: boolean;
// };
可以看到,虽然我们已经修改过了 description 属性,但是在交叉的过程当中被覆盖了,所以我们需要做一个排除,这里就要借用到上一篇中介绍的 Omit 工具类型:
type MyReadonly2<T, K extends keyof T> = {
readonly [P in K]: T[P];
} & Omit<T, K>;
type ASD = MyReadonly2<Todo3, "title" | "description">;
type SAS = {
[P in keyof ASD]: ASD[P];
};
// type SAS = {
// readonly title: string;
// readonly description?: string | undefined;
// completed: boolean;
// }
到此,我们就完成了 K 值存在的这种情况,那么要是不存在的时候,我们就需要进行特殊处理,TS 的 泛型语法中,提供给了我们默认值这种方法来进行未传入泛型参数时做一个处理,具体的使用方法就是在泛型后面加上等号和默认的值,这里我们可以将 K 默认值设为 T 的全部键值 也就是 Keyof T 这样未传入的时候默认是所有的键,也就满足了题目的全都改变这个要求:
type MyReadonly2<T, K extends keyof T = keyof T> = {
readonly [P in K]: T[P];
} & Omit<T, K>;
type ASD = MyReadonly2<Todo3>;
type SAS = {
[P in keyof ASD]: ASD[P];
};
// type SAS = {
// readonly title: string;
// readonly description?: string | undefined;
// readonly completed: boolean;
// }
到这里所有的 测试 case 就全部通过了。
知识点
关于上述提到了部分的知识点:
- 交叉类型的使用
- Omit 工具方法
- 泛型默认值
交叉类型的使用能够参考之前的文章
至于 Omit 类型就是上一篇的刷题文章 MyOmit:
泛型默认值这一块可以去翻阅一下官方文档或者百度看一下别的大佬的解释,这里就不做过多讲述了。
总结
今天我们做完了中等的第三题,涉及到了交叉类型的使用,对于大多数工具类型来说,灵活运用联合和交叉的特性是十分重要的,可以说联合和交叉的特性也是作为 TS 体操的一个基础中的基础。