已更新以反映更好的递归条件类型支持
截至TS 4.7,TypeScript中仍然没有正则表达式验证的字符串类型。 模板字面量类型 处理了其中一些用例,但不是全部。如果您有这样的情况,即模板字面量类型不足以解决问题,则可以前往microsoft/TypeScript#41160并描述您的用例。使用正则表达式类型表达“最大长度为N
字符的字符串”对于某个N extends number
来说是非常容易的,但使用模板字面量却不容易实现。
尽管如此,让我们看看我们能接近多少。
一个主要障碍摆在了路上。 TypeScript无法轻松地将所有长度小于N
的字符串集表示为特定类型StringsOfLengthUpTo<N>
。概念上,任何给定的StringsOfLengthUpTo<N>
都是一个大的联合类型,但由于编译器对超过~10,000个成员的联合类型反感,因此您只能以这种方式描述长度为几个字符的字符串。假设您想支持7位可打印ASCII字符集的95个字符,则可以表示StringsOfLengthUpTo<0>
,StringsOfLengthUpTo<1>
,甚至StringsOfLengthUpTo<2>
。但是StringsOfLengthUpTo<3>
将超过编译器的容量,因为它将是800,000多个成员的联合。因此,我们必须放弃具体类型。
相反,我们可以将我们的类型视为泛型中使用的约束。我们需要一个类型,例如TruncateTo<T,N>
,它接受类型T extends string
和N extends number
并返回截断为N
个字符的T
。然后我们可以约束T extends TruncateTo<T,N>
,编译器会自动警告过长的字符串。
以前,浅层递归限制会阻止我们编写TruncateTo<T, N>
,其中N
大约大于20左右,但是TypeScript 4.5引入了对条件类型尾递归消除的支持。这意味着我们可以通过添加一些额外的累加器参数来编写TruncateTo<T, N>
,如下所示:
type TruncateTo<T extends string, N extends number,
L extends any[] = [], A extends string = ""> =
N extends L['length'] ? A :
T extends `${infer F}${infer R}` ? (
TruncateTo<R, N, [0, ...L], `${A}${F}`>
) :
A
这个方法通过使用一个
A
累加器来存储我们正在构建的字符串,并且使用一个类似数组的累加器
L
来跟踪
A
字符串的长度(字符串文字类型没有强类型的
length
属性,请参见相关请求
ms/TS#34692)。我们逐个字符地构建
A
,直到我们要么用完原始字符串,要么达到了长度
N
。让我们看看它的实际应用:
type Fifteen = TruncateTo<"12345678901234567890", 15>
// type Fifteen = "123456789012345"
type TwentyFive = TruncateTo<"123456789012345678901234567", 25>
// type TwentyFive = "1234567890123456789012345"
我们无法直接编写
T extends TruncateTo<T, N>
,因为TypeScript会抱怨这是一个循环约束。但是我们至少可以编写一个辅助函数,如下所示:
const atMostN = <T extends string, N extends number>(
num: N, str: T extends TruncateTo<T, N> ? T : TruncateTo<T, N>
) => str;
然后你可以调用atMostN(32, "someStringLiteral")
,它会根据字符串字面量参数的长度成功或警告。请注意,str
输入是一种奇怪的条件类型,其唯一目的是避免循环约束。 T
从str
推断出来,然后与TruncateTo<T,N>
进行检查。如果成功,那太棒了。否则,我们将给str
赋予TruncateTo<T,N>
类型,并看到错误消息。 它的工作原理如下:
const okay = atMostN(32, "ThisStringIs28CharactersLong");
type Okay = typeof okay;
const bad = atMostN(32, "ThisStringHasALengthOf34Characters");
type Bad = typeof bad;
这值得吗?或许吧。原始答案需要做一些不太好的事情,才能得到一个固定长度的检查。现在的答案还不错,但仍然需要大量努力才能获得编译时检查。所以,您可能仍然需要使用正则表达式验证的字符串类型。
代码的Playground链接