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


    前言

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

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

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

    Pick [easy]

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

    interface Todo {title: stringdescription: stringcompleted: boolean
    }
    
    type TodoPreview = MyPick
    
    const todo: TodoPreview = {title: 'Clean room',completed: false,
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    测试用例如下

    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [Expect>>,Expect>>,// @ts-expect-errorMyPick,
    ]
    
    interface Todo {title: stringdescription: stringcompleted: boolean
    }
    
    interface Expected1 {title: string
    }
    
    interface Expected2 {title: stringcompleted: boolean
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在我们未学习 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;
    } 
    
    • 1
    • 2

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

    返回一个对象

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

    type MyPick = {}; 
    
    • 1

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

    遍历 K

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

    type MyPick = {[P in K]: T[P] 
    }; 
    
    • 1
    • 2

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

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

    限制 K 的范围

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

    type MyPick = {[P in K]: T[P] 
    }; 
    
    • 1
    • 2

    需要注意的是 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' //未通过,报错 
    
    • 1
    • 2
    • 3

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

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

    Omit [medium]

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

    interface Todo {title: stringdescription: stringcompleted: boolean
    }
    
    type TodoPreview = MyOmit
    
    const todo: TodoPreview = {completed: false,
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    测试用例如下:

    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [Expect>>,Expect>>,
    ]
    
    // @ts-expect-error
    type error = MyOmit
    
    interface Todo {title: stringdescription: stringcompleted: boolean
    }
    
    interface Expected1 {title: stringcompleted: boolean
    }
    
    interface Expected2 {title: string
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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

    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
    } 
    
    • 1
    • 2

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

    返回一个对象

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

    type MyOmit = {} 
    
    • 1

    遍历 T

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

    type MyOmit = {[P in keyof T]: T[P]
    } 
    
    • 1
    • 2

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

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

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

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

    type test = {[P in keyof T]: T[P]
    }
    
    type foo = string | never
    type r = test2 // type a = string 
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

    type MyOmit = {[P in keyof T as P extends K ? never : P]: T[P]
    } 
    
    • 1
    • 2

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

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

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

    type MyOmit = {[P in keyof T as P extends K ? never : P]: T[P]
    } 
    
    • 1
    • 2

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

    总结

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

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

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

    5.使用 in 来进行遍历


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



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

  • 相关阅读:
    技术实践|高斯集群服务器双缺省网关故障分析
    第一章 调度系统架构设计之线程池创建
    安全狗陈奋:数据安全需要建立在传统网络安全基础之上
    APS智能排产在电缆行业的应用
    MQ - 08 基础篇_消费者客户端SDK设计(下)
    飞机机翼机身对接结构数值计算分析(ANSYS)
    OLOv9与YOLOv8性能差别详解
    学用 DevChat 的 VSCode 插件,体验AI智能编程工具 (一)
    PyInstaller 动指定这些库的路径
    2022年数维杯国际大学生数学建模挑战赛开赛公告
  • 原文地址:https://blog.csdn.net/pfourfire/article/details/126788750