携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第17天,点击查看活动详情
不知道你的类型体操练得咋样了呢?通过下面这个类型体操来检验一下吧!
场景说明
url中的query params大家应该都不陌生吧?就是长这样子的:
a=1&a=2&b=3&d=4
而我们今天的目标,就是要通过类型体操,把这样的query params字符串转换成索引类型,并且同一个索引的不同值需要用数组保存
最终效果就是:
{
a: ["1", "2"],
b: "3",
c: "4"
}
这涉及到多个类型体操的基本知识点,我们先来分析一下大概的完成类型体操的思路
思路分析
- 首先我们需要把
&分隔的每个param提取出来,也就是我们需要得到a=1、b=3这样的项 - 把提取出的每个
param转成索引类型,也就是{ a: "1" }这样的形式 - 对于同一个索引由不同值的,要转成数组存储,也就是
{ a: ["1", "2"] }这样的形式
核心就是这三步了,那么每一步都应该怎么实现,涉及哪些知识点呢?
知识点分析
extends + infer 进行字符串模式匹配
对于第一点,需要按照&分割提取字符串,这就涉及到模式匹配的知识点了,可以通过extends条件类型进行约束判断,然后将匹配到的结果通过infer保存起来,不严谨地讲,可以理解为通过infer声明一个类型变量将我们想要的结果保存起来
通过递归模拟循环
由于一个query params中有多个键值对,所以我们应当循环遍历它们来构建我们的索引类型,那么如何实现循环的效果呢?
答案是用递归来模拟,ts没有显式的循环语法,因此我们只能通过递归,当能够通过&分隔提取每一个param的时候就一直递归进行提取,直到提取到最后一个param,已经没有&进行分隔了,就可以退出递归的过程了
用多个简单工具类型组合实现一个复杂工具类型
由于我们的这个工具类型实现起来涉及到的功能比较多,所以合理的做法是需要将其拆分成多个简单的工具类型,每个工具类型完成单一的工作,最后像搭积木一样将它们组合起来即可
那么需要拆分出哪些简单工具类型呢?
ParseParam<T>,这个工具类型负责将a=1这样的param解析成{ a: "1" }这样的单一索引类型MergeParams<Param, Rest>,负责将已经解析得到的索引类型合并为一个大的索引类型MergeValues<One, Other>,负责将同一个索引的多个不同值合并成数组返回
通过以上分析后,我们就可以开始做操了!
完成体操代码
/**
* @description 解析 query params
*
* a=1&a=2&b=3&c=4 ==> { a: ["1", "2"], b: "3", c: "4" }
*/
type ParseQueryString<T extends string> =
// 模式匹配提取 a=1&a=2&b=3&c=4 中的 a=1 和 a=2&b=3&c=4
T extends `${infer Param}&${infer Rest}`
? // 匹配成功则合并他们
// ParseParam<'a=1'> => { a: "1" }
// ParseQueryString<'a=2&b=3&c=4'> => { a: "2", b: "3", c: "4" }
// 合并后 => { a: ["1", "2"], b: "3", c: "4" }
MergeParams<ParseParam<Param>, ParseQueryString<Rest>>
: // 匹配失败 说明已经解析到最后一个 param string 了 也就是 c=4
// 这时候直接单独解析它即可
ParseParam<T>
// 合并两个已解析的 param 索引类型
// { a: "1" } 和 { a: "2", b: "3", c: "4" } => { a: ["1", "2"], b: "3", c: "4" }
type MergeParams<
Param extends Record<string, any>,
Rest extends Record<string, any>,
> = {
// 遍历 Param 和 Rest 的 key
// 如果 key 同时存在于 Param 和 Rest 中 则需要调用 MergeValues 进行合并
// 否则就返回值即可
[Key in keyof Param | keyof Rest]: Key extends keyof Param
? Key extends keyof Rest
? MergeValues<Param[Key], Rest[Key]>
: Param[Key]
: Key extends keyof Rest
? Rest[Key]
: never
}
// 合并索引类型的值 如果是同一个值就不需要合并 直接返回即可
// 否则就要构造一个数组去保存
type MergeValues<One, Other> = One extends Other
? One
: Other extends unknown[]
? [One, ...Other]
: [One, Other]
// 解析 a=1 为 { a: "1" }
// 要用可索引签名才可以正常添加索引项
type ParseParam<Param extends string> =
Param extends `${infer Key}=${infer Value}` ? { [K in Key]: Value } : {}
// type Res = {
// a: ['1', '2']
// b: '3'
// c: '4'
// }
type Res = ParseQueryString<'a=1&a=2&b=3&c=4'>