使用模板文字类型解析字符串的方法大致有两种,它们都有一些注意事项:
逐个字符
到目前为止,最简单的方法是逐个字符地遍历字符串,如下所示:
type Something<T extends string> =
T extends `${infer C0}${infer R}` ? Combine<C0, Something<R>> :
BaseCase
whereC0
是字符串的第一个字符,R
其余的是。当您有两个infer
相邻的占位符时,编译器将推断第一个占位符的单个字符。这很简单,因为您总是知道会发生什么C0
。主要的警告是递归限制足够浅,这样的东西只会对长度不超过 20 的字符串起作用。您可以稍微修改它,使接受的最大字符串长度大致增加一倍或三倍,方法是在可能的情况下一次抓取两个或三个字符。例如:
type Something<T extends string> =
T extends `${infer C0}${infer C1}${infer R}` ? Combine<C0, Combine<C1, Something<R>>> :
T extends `${infer C0}${infer R}` ? Combine<C0, Something<R>> :
BaseCase
如果我们DashUppercase
使用这种方法,我们会得到:
type _DU<T extends string> = T extends Lowercase<T> ? T : `-${Lowercase<T>}`;
type DashUppercase<T extends string> =
T extends `${infer C0}${infer R}` ? `${_DU<C0>}${DashUppercase<R>}` :
""
这很简单。请注意,我没有保留大写字符列表,而是将大写字符标识T
为当您应用Lowercase<T>
它时会发生变化的东西。它适用于您的用例(我认为):
type A = DashUppercase<'oneTwoThree'> // "one-two-three"
type B = DashUppercase<'OneTwoThree'> // "-one-two-three"
type C = DashUppercase<'23Skidoo'> // "23-skidoo"
但是,正如我所说,长字符串会导致递归警告:
type Oops =
DashUppercase<'abcdefghijklmnopqrstuvwxyz'> // error, too long, excessively deep
如果我将其更改为二乘二,您将得到:
type DashUppercase<T extends string> =
T extends `${infer C0}${infer C1}${infer R}` ?
`${_DU<C0>}${_DU<C1>}${DashUppercase<R>}` :
T extends `${infer C0}${infer R}` ? `${_DU<C0>}${DashUppercase<R>}` :
""
type Okay = DashUppercase<'abcdefghijklmnopqrstuvwxyz'> // okay now
type Oops =
DashUppercase<'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvw'> // too long
对于您的用例而言,这可能足够长,也可能不够长。
分隔符分割
您正在尝试做的另一种方法是在分隔符处拆分字符串。这可能有一个看起来像其中的部分T extends `${infer F}${D}${infer R}`
,其中F
是字符串的第一部分,D
是一些定界符或定界符的联合,R
是字符串的其余部分。这样做的吸引力在于,假设您的输入字符串中的分隔符很少,您将不会遇到递归限制。主要缺点是它很复杂,很难知道会出现什么F
和R
。
如果有多个候选者,编译器将倾向于在这里推断联合;没有什么能阻止F
包含D
. 这就是在你的定义中咬你的东西。因此,您将希望从F
包含 aD
本身的任何内容中消除,然后如果您需要识别分隔符(如果D
是联合并且您关心匹配的成员),则需要再次推断字符串的其余部分。
在这个 GitHub 评论中,我编写了一个通用Break<T, D>
实用程序,它接受一个字符串T
并将其拆分为形式的元组,[F, R]
其中F
最长的前缀不包含D
,并且R
是字符串的其余部分。它看起来像这样:
type Break<T extends string, D extends string> = (
string extends T ? [string, string] : (
T extends `${infer F}${D}${infer R}` ? (
F extends `${infer X}${D}${infer Y}` ? never : (
T extends `${F}${infer R}` ? [F, R] : never
)
) : [T, ""]
)
);
呸!但它似乎确实有效。
有了这个,我们可以DashUppercase
这样写:
type DashUppercase<T extends string> =
Break<T, UpperAlpha> extends [infer L, infer R] ?
L extends string ? R extends `${infer U}${infer RR}` ?
`${L}-${Lowercase<U>}${DashUppercase<RR>}` : L :
never : never;
所以我们Break
T
通过UpperAlpha
分隔符集(我们确实需要它;在我看来,反对这种方法的另一点)。返回L
的不包含大写字符,所以我们总是可以在字符串的开头返回它。 R
要么以大写字符开头,我们可以将其剥离和转换,要么为空。
这现在适用于之前的所有测试,包括长测试:
type A = DashUppercase<'oneTwoThree'> // "one-two-three"
type B = DashUppercase<'OneTwoThree'> // "-one-two-three"
type C = DashUppercase<'23Skidoo'> // "23-skidoo"
type Okay = DashUppercase<'abcdefghijklmnopqrstuvwxyz'> // okay now
type Okay2 =
DashUppercase<'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvw'> // also okay
当然,如果你最终有很多大写字符,那么这将达到递归限制,甚至比以前更快,因为有多个条件检查Break
:
type Oops = DashUppercase<'ABCDEFGHIJKL'> // too long
您可以通过在搜索大写字符和搜索非大写字符之间来回切换来避免此限制,但在这一点上,我宁愿尖叫也不愿尝试写下它。特别是因为“非大写字符”列表很长。
所以你去。您可以单独或在小组中解析字符并获得仅适用于相当短的字符串的简单定义,或者搜索分隔符并获得适用于较长字符串的复杂且复杂的定义。
Playground 代码链接