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

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

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

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

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

类型体操基础

模式匹配

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

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

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

构造

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

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

递归

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

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

type ReverseStr< 
    Str extends string,
    Result extends string = '' 
> = Str extends `${infer First}${infer Rest}` 
    ? ReverseStr<Rest, `${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 ParseQueryString<Str extends string>
    = Str extends `${infer Param}&${infer Rest}`
        ? MergeParams<ParseParam<Param>, ParseQueryString<Rest>> 
        : ParseParam<Str>;
复制代码

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

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

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

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

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

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

type ParseParam<Param extends string> 
    = Param extends `${infer Key}=${infer Value}` 
        ? { [K in Key]: Value } 
        : {};
复制代码

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

先来测试下这个 ParseParam:

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

合并的部分就是 MergeParams:

type MergeParams<
    OneParam extends object,
    OtherParam extends object
> = {
  [Key in keyof OneParam | keyof OtherParam]: 
    Key extends keyof OneParam
        ? Key extends keyof OtherParam
            ? MergeValues<OneParam[Key], OtherParam[Key]>
            : 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, Other> = 
    One extends Other 
        ? One
        : Other extends unknown[]
            ? [One, ...Other]
            : [One, Other];
复制代码

测试下 MergeValues:

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

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

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

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

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

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

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


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

优秀的个人博客,低调大师

微信关注我们

原文链接:https://my.oschina.net/u/5079097/blog/5444753

转载内容版权归作者及来源网站所有!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

相关文章

发表评论

资源下载

更多资源
Apache Tomcat7、8、9(Java Web服务器)

Apache Tomcat7、8、9(Java Web服务器)

Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。因为Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应用服务器。

Eclipse(集成开发环境)

Eclipse(集成开发环境)

Eclipse 是一个开放源代码的、基于Java的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。幸运的是,Eclipse 附带了一个标准的插件集,包括Java开发工具(Java Development Kit,JDK)。

Java Development Kit(Java开发工具)

Java Development Kit(Java开发工具)

JDK是 Java 语言的软件开发工具包,主要用于移动设备、嵌入式设备上的java应用程序。JDK是整个java开发的核心,它包含了JAVA的运行环境(JVM+Java系统类库)和JAVA工具。

Sublime Text 一个代码编辑器

Sublime Text 一个代码编辑器

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。