[TS 类型体操] 初体验之Pick 与 Omit

本文介绍了如何使用Typescript的Pick<T, K>和Omit<T, K>来操作对象类型。通过逐步解析,展示了如何实现这两个泛型,包括返回指定属性的对象、遍历类型、限制属性范围以及移除特定属性。文章通过实例讲解了类型体操在类型系统中的应用,帮助开发者深入理解Typescript。" 106776053,8783232,MySQL vs MongoDB:数据管理解决方案对比,"['数据库', '大数据', '编程语言', 'mysql', 'mongodb']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

Typescript 逐渐成为前端工程师必备的技能,如何更好的理解 Typescript,使用它解析标注更为复杂的类型?类型体操是一个很好的选择。

如何利用 Typescript 的特性来实现一系列令人眼界大开的操作,这个库 type-challenges 将告诉你答案

在这篇文章中,我将从最基础的开始,来一步步完善我们的 Typescript 操作手段

Pick<T, K> [easy]

在这道题目中,我们将实现一个内置的泛型 Pick<T, K>,它的功能是从 T 中选出 K 中的属性,并将选中的属性返回。 问题链接

interface Todo {title: stringdescription: stringcompleted: boolean
}

type TodoPreview = MyPick<Todo, 'title' | 'completed'>

const todo: TodoPreview = {title: 'Clean room',completed: false,
} 

测试用例如下

import type { Equal, Expect } from '@type-challenges/utils'

type cases = [Expect<Equal<Expected1, MyPick<Todo, 'title'>>>,Expect<Equal<Expected2, MyPick<Todo, 'title' | 'completed'>>>,// @ts-expect-errorMyPick<Todo, 'title' | 'completed' | 'invalid'>,
]

interface Todo {title: stringdescription: stringcompleted: boolean
}

interface Expected1 {title: string
}

interface Expected2 {title: stringcompleted: boolean
} 

在我们未学习 TS 类型体操之前,看到这道题可能会有点蒙,我们不妨先使用 JS 的思维来分解一下这道题,先使用 JS 的代码来解决这道题。

根据题意以及测试用例,我们可以得到以下思路:

1.我们需要返回一个对象
2.我们需要遍历 K
3.如果 K 中的元素也在 T 中则将结果添加进 result
4.如果 K 中有 T 中没有的元素,需要抛出一个错误

根据以上信息我们可以很容易的写出以下代码

function myPick(todo, keys) {let result = {};keys.forEach((key) => {if (key in todo) picked[key] = todo[key]; });return result;
} 

JS 版本的 Pick 已经完成,我们来一步步完成 TS 版本的 Pick

返回一个对象

在 TS 中我们想要返回一个对象,可以直接这样写

type MyPick<T, K> = {}; 

这样我们第一个点已经完成,接下来我们开始完成第二个点

遍历 K

在 TS 中我们想要遍历一个对象可以使用 Mapped type 来进行遍历,即

type MyPick<T, K> = {[P in K]: T[P] 
}; 

TS 中,当我们想遍历一个对象的属性,我们可以使用 [P in K] 的方式来进行遍历。其中 P 代表着要遍历的属性中的一个。值得注意的是,在 in 右边的 K 必须是一个 union, 当他不是一个 union 时,我们需要使用 keyof 来将 K 转换为一个 union

完成这步之后,我们就可以利用 T[P] 来得到该元素的类型

限制 K 的范围

完成上一步时,我们可以发现我们已经可以完成大部分测试用例,但并未对 K 的范围进行限制。这时候我们就可以利用 extends 来进行条件约束

type MyPick<T, K extends keyof T> = {[P in K]: T[P] 
}; 

需要注意的是 K 是一个 union,keyof T 也将 T 中的属性转换为一个 union。当我们使用 extends 来进行条件约束的时候,TS 会使用 union 分发的特性自动遍历 union K 中的属性与 keyof T 中的属性进行比较。

假设 K 为 ‘title’ | ‘completed’ | ‘invalid’ ,T 为 ‘title’ | ‘completed’ | ‘description’。它的过程如下

step1:'title' extends 'title' | 'completed' | 'description' //通过
step2:'completed' extends 'title' | 'completed' | 'description' //通过
step3:'invalid' extends 'title' | 'completed' | 'description' //未通过,报错 

如果比较成功则通过,失败则报错,这样我们就实现了所有的关键步骤,通过了所有的测试用例。

通过一步步的拆解,我们可以看到这道题还是很简单的。那让我们来看下一道题

Omit<T, K> [medium]

在这道题中,我们需要实现一个内置泛型 Omit<T, K>,它的功能是从 T 中删除 K 包括的元素,最后将结果以对象的形式返回,问题链接

interface Todo {title: stringdescription: stringcompleted: boolean
}

type TodoPreview = MyOmit<Todo, 'description' | 'title'>

const todo: TodoPreview = {completed: false,
} 

测试用例如下:

import type { Equal, Expect } from '@type-challenges/utils'

type cases = [Expect<Equal<Expected1, MyOmit<Todo, 'description'>>>,Expect<Equal<Expected2, MyOmit<Todo, 'description' | 'completed'>>>,
]

// @ts-expect-error
type error = MyOmit<Todo, 'description' | 'invalid'>

interface Todo {title: stringdescription: stringcompleted: boolean
}

interface Expected1 {title: stringcompleted: boolean
}

interface Expected2 {title: string
} 

同上一道题一样,我们先分析一下他有哪些点需要实现

1.需要返回一个对象
2.遍历 T
3.如果 T 中的元素不在 K 中,则将该元素添加到结果中
4.如果 K 中包括 T 中没有的元素,则报错

同样,我们先使用 JS 实现一边来加强我们的理解

function omit(T, K) {let resultT.foreach((item)=>{if(!K.includes(item)) {result.push(item)}})return result
} 

我们来一步步实现上面总结的步骤

返回一个对象

通过 Pick 我们已经对这个很了解了

type MyOmit<T, K> = {} 

遍历 T

通过上一道题,我们也可以很容易的写出一下 TS 代码

type MyOmit<T, K> = {[P in keyof T]: T[P]
} 

需要注意的是,这次我们遍历的是 T 而不是 K,因为 T 是一个对象,所以我们要使用 keyof 来将他转化为一个 union

如果 T 中的元素不在 K 中,则将该元素添加到结果中

到这一步我们该怎么实现呢?我们想一下有什么方法可以使一个元素消失。对,我们可以使用 as never 来使一个元素消失。

在 TS 中如果一个 union 中的元素是一个 never 类型的,那么 TS 认为这个元素是一个空值,会返回去除这个值之后的结果。

type test<T> = {[P in keyof T]: T[P]
}

type foo = string | never
type r = test2<foo> // type a = string 

所以根据以上特点,而在遍历 T 的过程中,一个 P 就是 union 中的一个元素,我们将这个元素断言成 never 那么他就会被 TS 看作是一个空的 union 元素,一个空的 union 元素自然不会被当作 key,也不会被 T[P] 所选中。这样我们就可以实现从 T 中删除一个元素的功能

type MyOmit<T, K> = {[P in keyof T as P extends K ? never : P]: T[P]
} 

以上就是该功能点的全部实现,注意的是,我们根据上一题已经知道了,当 extends 两边是一个 union 时,就会触发 union 的分发特性(Distributive),P 中的元素会自动与 K 中的元素进行对比,如果对比通过了,说明该元素是要被删除的,则将该元素断言成 never,如果未通过,还按原来的结果处理

当 K 中不包括 T 中的属性报错

根据第一题,很容易的就可以实现

type MyOmit<T, K extends keyof T> = {[P in keyof T as P extends K ? never : P]: T[P]
} 

至此,Pick 与 Omit 已经全部实现完成

总结

1.使用 keyof 对类型进行提取
2.使用 extends 对类型进行限制的两种方法

3.使用 T[P] 来获得类型

4.as never 来将 union 中的元素去除

5.使用 in 来进行遍历


这是我自己的练题仓库,里面会总结我在练习类型体操中遇到的相关问题以及相关知识点,如果这对你有帮助的话就给我一个 star 吧😄最后,为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值