热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

TS类型体操:图解一个复杂高级类型

ts,类型,体操,图解,一

今天就来做个高难度的体操,它会综合运用模式匹配、构造、递归等套路,对提升类型编程水平很有帮助。

我们要实现的高级类型如下:

它的类型参数是参数字符串 query string,会返回解析出的参数对象,如果有同名的参数,会把值做合并。

先不着急实现,我们先回顾下相关的类型体操基础:

类型体操基础

模式匹配

模式匹配是指用一个类型匹配一个模式类型来提取其中的部分类型到 infer 声明的局部变量中。

比如提取 a=b 中的 a 和 b:

这种模式匹配的套路在数组、字符串、函数等类型中都有很多应用。

构造

映射类型用于生成索引类型,生成的过程中可以对索引或者索引值做一些修改。

比如指定 key 和 value 来生成一个索引类型:

递归

TypeScript 高级类型支持递归,可以处理数量不确定的问题。

比如不确定长度的字符串的反转:

type ReverseStrextends string, Result extends string = '' > = Str extends `${infer First}${infer Rest}` ? ReverseStr`${First}${Result}`> : Result; 

简单了解下模式匹配、构造、递归都是什么之后,就可以开始实现这个复杂的高级类型 ParseQueryString 了:

思路分析

假设有这样一个 query string: a=1&a=2&b=3&c=4

我们要首先把它分成 4 部分:也就是 a=1、a=2、b=3、c=4。这个就是用通过上面讲的模式匹配来提取。

每一部分又可以进一步处理,提取出 key value 构造成索引类型,比如 a=1 就可以通过模式匹配提取出 a、1,然后构造成索引类型 {a: 1}。

这样就有了 4 个索引类型 {a:1}、{a:2}、{b:3}、{c:4}。

结下来把它合并成一个就可以了,合并的时候如果有相同的 key 的值,要放到数组里。

就产生了最终的索引类型:{a: [1,2], b: 3, c: 4}

整体流程是这样的:

其中第一步并不知道有多少个 a=1、b=2 这种 query param,所以要递归的做模式匹配来提取。

这就是这个高级类型的实现思路。

下面我们具体来写一下:

代码实现

我们按照上图的顺序来实现,首先提取 query string 中的每一个 query param:

query param 数量不确定,所以要用递归:

type ParseQueryStringextends string> = Str extends `${infer Param}&${infer Rest}` ? MergeParams, ParseQueryString> : ParseParam; 

类型参数 Str 为待处理的 query string。

通过模式匹配提取其中第一个 query param 到 infer 声明的局部变量 Param 中,剩余的字符串放到 Rest 中。

用 ParseParam 来处理 Param,剩余的递归处理,最后把它们合并到一起,也就是 MergeParams, ParseQueryString> 。

如果模式匹配不满足,说明还剩下最后一个 query param 了,也用 ParseParam 处理。

然后分别实现每一个 query param 的 parse:

这个就是用模式匹配提取 key 和 value,然后构造一个索引类型:

type ParseParamextends string> = Param extends `${infer Key}=${infer Value}` ? { [K in Key]: Value } : {}; 

这里构造索引类型用的就是映射类型的语法。

先来测试下这个 ParseParam:

做完每一个 query param 的解析了,之后把它们合并到一起就行:

合并的部分就是 MergeParams:

type MergeParamsextends object, OtherParam extends object > = { [Key in keyof OneParam | keyof OtherParam]: Key extends keyof OneParam ? Key extends keyof OtherParam ? MergeValues : OneParam[Key] : Key extends keyof OtherParam ? OtherParam[Key] : never } 

两个索引类型的合并也是要用映射类型的语法构造一个新的索引类型。

key 是取自两者也就是 key in keyof OneParam | keyof OtherParam。

value 要分两种情况:

  • 如果两个索引类型都有的 key,就要做合并,也就是 MergeValues。
  • 如果只有其中一个索引类型有,那就取它的值,也就是 OtherParam[key] 或者 OneParam[Key]。

合并的时候,如果两者一样就返回任意一个,如果不一样,就合并到数组里返回,也就是 [One, Other]。如果本来是数组的话,那就是数组的合并 [One, ...Other]。

type MergeValues = One extends Other ? One : Other extends unknown[] ? [One, ...Other] : [One, Other]; 

测试下 MergeValues:

这样,我们就实现了整个高级类型,整体测试下:

这个案例综合运用到了递归、模式匹配、构造的套路,还是比较复杂的。

可以对照着这张图来看下完整代码:

type ParseParamextends string> = Param extends `${infer Key}=${infer Value}` ? { [K in Key]: Value } : {}; type MergeValues = One extends Other ? One : Other extends unknown[] ? [One, ...Other] : [One, Other]; type MergeParamsextends object, OtherParam extends object > = { [Key in keyof OneParam | keyof OtherParam]: Key extends keyof OneParam ? Key extends keyof OtherParam ? MergeValues : OneParam[Key] : Key extends keyof OtherParam ? OtherParam[Key] : never } type ParseQueryStringextends string> = Str extends `${infer Param}&${infer Rest}` ? MergeParams, ParseQueryString> : ParseParam; type ParseQueryStringResult = ParseQueryString<'a=1&a=2&b=2&c=3'>; 

总结

我们首先复习了下 3 种类型体操的套路:

  • 模式匹配:一个类型匹配一个模式类型,提取其中的部分类型到 infer 声明的局部变量中
  • 构造:通过映射类型的语法来构造新的索引类型,构造过程中可以对索引和值做一些修改
  • 递归:当处理数量不确定的类型时,可以每次只处理一个,剩下的递归来做

然后用这些套路来实现了一个 ParseQueryString 的复杂高级类型。

如果能独立实现这个高级类型,说明你对这三种类型体操的套路掌握的就挺不错的了。

最后

如果你觉得此文对你有一丁点帮助,点个赞。或者可以加入我的开发交流群:1025263163相互学习,我们会有专业的技术答疑解惑

如果你觉得这篇文章对你有点用的话,麻烦请给我们的开源项目点点star:http://github.crmeb.net/u/defu不胜感激 !

PHP学习手册:https://doc.crmeb.com
技术交流论坛:https://q.crmeb.com


推荐阅读
  • 在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。 ... [详细]
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
  • 深入解析Spring启动过程
    本文详细介绍了Spring框架的启动流程,帮助开发者理解其内部机制。通过具体示例和代码片段,解释了Bean定义、工厂类、读取器以及条件评估等关键概念,使读者能够更全面地掌握Spring的初始化过程。 ... [详细]
  • PHP 过滤器详解
    本文深入探讨了 PHP 中的过滤器机制,包括常见的 $_SERVER 变量、filter_has_var() 函数、filter_id() 函数、filter_input() 函数及其数组形式、filter_list() 函数以及 filter_var() 和其数组形式。同时,详细介绍了各种过滤器的用途和用法。 ... [详细]
  • 本文详细介绍了 iBatis.NET 中的 Iterate 元素,它用于遍历集合并重复生成每个项目的主体内容。通过该元素,可以实现类似于 foreach 的功能,尽管 iBatis.NET 并未直接提供 foreach 标签。 ... [详细]
  • 本文详细探讨了HTML表单中GET和POST请求的区别,包括它们的工作原理、数据传输方式、安全性及适用场景。同时,通过实例展示了如何在Servlet中处理这两种请求。 ... [详细]
  • 深入解析Redis内存对象模型
    本文详细介绍了Redis内存对象模型的关键知识点,包括内存统计、内存分配、数据存储细节及优化策略。通过实际案例和专业分析,帮助读者全面理解Redis内存管理机制。 ... [详细]
  • 对象自省自省在计算机编程领域里,是指在运行时判断一个对象的类型和能力。dir能够返回一个列表,列举了一个对象所拥有的属性和方法。my_list[ ... [详细]
  • JavaScript 基础语法指南
    本文详细介绍了 JavaScript 的基础语法,包括变量、数据类型、运算符、语句和函数等内容,旨在为初学者提供全面的入门指导。 ... [详细]
  • 本文详细介绍了C++中map容器的多种删除和交换操作,包括clear、erase、swap、extract和merge方法,并提供了完整的代码示例。 ... [详细]
  • 实用正则表达式有哪些
    小编给大家分享一下实用正则表达式有哪些,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下 ... [详细]
  • 本文详细介绍了Java中org.apache.logging.log4j.spi.AbstractLogger类的logIfEnabled()方法,包括其功能、参数说明及实际代码示例。通过这些示例,读者可以更好地掌握如何在项目中使用该方法进行日志记录。 ... [详细]
  • 版本控制工具——Git常用操作(下)
    本文由云+社区发表作者:工程师小熊摘要:上一集我们一起入门学习了git的基本概念和git常用的操作,包括提交和同步代码、使用分支、出现代码冲突的解决办法、紧急保存现场和恢复 ... [详细]
  • 由二叉树到贪心算法
    二叉树很重要树是数据结构中的重中之重,尤其以各类二叉树为学习的难点。单就面试而言,在 ... [详细]
  • 本文探讨了如何利用 Python 的 PyPDF2 库在内存中高效地合并多个 PDF 文件,并讨论了相关的内存管理问题及优化策略。 ... [详细]
author-avatar
勇气的爱_961
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有