写点什么

玩转 TypeScript--openInula 中的 TypeScript 实践(第一篇)

作者:openInula
  • 2024-01-11
    上海
  • 本文字数:1891 字

    阅读完需:约 6 分钟

玩转TypeScript--openInula中的TypeScript实践(第一篇)

openInula及其生态组件使用了 TypeScript 语言来进行编写,相比React使用的flow,TypeScript 具有更完善灵活的语法、支持函数式编程,另外通过 TypeScript 生成的index.d.ts也免于开发人员手动维护d.ts文件。在这个系列中我们将选取多个openInulaTypeScript 的实践来进行介绍。

TypeScript 类型体操实现路由参数自动补全

今天介绍的是使用 TypeScript 类型体操完成路由参数的自动补全,相信大家在阅读inula-router的源码时会发现有这样的一段代码写在inula-router的类型声明中:


// 片段1type ClearLeading<U extends string> = U extends `/${infer R}` ? ClearLeading<R> : U;type ClearTailing<U extends string> = U extends `${infer L}/` ? ClearTailing<L> : U;
// 片段2type ParseParam<Param extends string> = Param extends `:${infer R}` ? { [K in R]: string } : {};
// 片段3type MergeParams<OneParam extends Record<string, any>, OtherParam extends Record<string, any>> = { readonly [Key in keyof OneParam | keyof OtherParam]?: string;};
// 片段4type ParseURLString<Str extends string> = Str extends `${infer Param}/${infer Rest}` ? MergeParams<ParseParam<Param>, ParseURLString<ClearLeading<Rest>>> : ParseParam<Str>;
// 解析URL中的动态参数,以实现TypeScript提示功能export type GetURLParams<U extends string> = ParseURLString<ClearLeading<ClearTailing<U>>>;
// Route.tsxfunction Route<Path extends string, P extends Record<string, any> = ParseURLString<ClearLeading<ClearTailing<Path>>>>(props: RouteProps<P, Path>)
复制代码


如果不看注释的话,对于不少 Typescript 的使用者来说,或许并不清楚上述的类型声明到底有什么作用。下面让我们来依次讲解每个类型的作用,带你玩转 TypeScript 类型体操。

片段 1:

首先要讲解的是 TypeScript 中的infer关键词,infer是 TypeScript4.1 引入的一个关键字,用于声明类型推断变量。它的作用是方便地从一个类型中提取出一个新的类型。在ClearLeadingClearTailing这两个函数中,传入的参数是U,如果U是字符串且以/开头或结尾,就继续递归调用自身,否则就返回U


type ClearLeading<U extends string> = U extends `/${infer R}` ? ClearLeading<R> : U;type ClearTailing<U extends string> = U extends `${infer L}/` ? ClearTailing<L> : U;
复制代码

运行效果:

type Res = ClearLeading<"///hello"> // "Hello"type Res2 = ClearTailing<"world///"> // "world"
复制代码

片段 2:

看懂了上面的结果,那也就可以很容易地看懂函数ParseParam了,ParseParam接受一个字符类型的参数,如果该字符串以:开头,返回一个键为Param,值为string的类型。


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

运行效果:

type Res3 = ParseParam<":id"> // {id: string}type Res4 = ParseParam<"id"> // {}
复制代码

片段 3

这段代码是一个用于合并对象的工具函数,合并后键值的类型均为只读的string,符合路由中参数的类型。


type MergeParams<OneParam extends Record<string, any>, OtherParam extends Record<string, any>> = {  readonly [Key in keyof OneParam | keyof OtherParam]?: string;};
复制代码

运行效果

type Res5 = MergeParams<{a:string}, {b:string}> // {readonly a?: string, readonly b?: string}type Res6 = MergeParams<{a:string,b:string}, {c:number}> // {readonly a?: string, readonly b?: string, readonly c?: string}
复制代码

片段 4:

最后我们来看类型ParseURLString,该函数接受一个字符串参数,如果该参数中存在/,则用第一个/把字符串分为ParamRest两部分,将Param部分传入ParseParam函数中,去除Rest开头的/后,进行递归调用,并使用工具函数MergeParamsParam部分解析出的结果进行合并。递归的出口就是接受一个不包含/的字符串,返回从该字符串解析出的对象。


type ParseURLString<Str extends string> = Str extends `${infer Param}/${infer Rest}`  ? MergeParams<ParseParam<Param>, ParseURLString<ClearLeading<Rest>>>  : ParseParam<Str>;  // 解析URL中的动态参数,以实现TypeScript提示功能export type GetURLParams<U extends string> = ParseURLString<ClearLeading<ClearTailing<U>>>;
复制代码

运行效果:

type Res7 = GetURLParams<"/home/:name/:id"> // {name?: string, id?: string}
复制代码

自动补全效果图:

通过上面这样的 TypeScript 类型体操再结合泛型的使用就可以实现下图中的效果了,可以在开发者使用时给出更为智能的补全信息:



发布于: 刚刚阅读数: 3
用户头像

openInula

关注

还未添加个人签名 2024-01-11 加入

还未添加个人简介

评论

发布
暂无评论
玩转TypeScript--openInula中的TypeScript实践(第一篇)_typescript_openInula_InfoQ写作社区