将字符串类型转换为字符类型数组

Convert string TYPE into array of chars TYPE

在 TypeScript 4.1 中,开发版本已经可以通过 npm 获得,支持 Recursive Conditional Types and Template literal types 创造了一些非常有趣的机会

假设我们有以下类型

// type is '0123456';
const actualString = '0123456';

任务

将字符串按字符拆分成新数组,但应保留数组元素的类型

// Unfortunately, type is string[]
const chars1 = actualString.split('');

// Throws error: string[] is not assignable ['0', '1', '2', '3', '4', '5', '6']
const chars2: ['0', '1', '2', '3', '4', '5', '6'] = actualString.split('');

我对这个的看法

type StringToChars<BASE extends string> = BASE extends `${infer _}`
  ? BASE extends `${infer FIRST_CHAR}${infer REST}` // BASE is inferable
    ? [FIRST_CHAR, ...StringToChars<REST>] // BASE has at least one character
    : [] // BASE is empty string
  : string[]; // BASE is simple string

// type is ['0', '1', '2', '3', '4', '5', '6']
type Chars = StringToChars<'0123456'>;

问题

此解决方案适用于少于 14 个字符的字符串。

// Throws: Type instantiation is excessively deep and possibly infinite. (ts2589)
type LargeCharsArray = StringToChars<'0123456789 01234'>

显然它遇到了打字稿类型递归限制,在检查第 14 个字符后它给我们留下了 [<first 14 characters>, ...any[]]

问题

这个递归类型调用看起来很糟糕,所以我想知道,有没有更可靠的方法将字符串类型转换为字符类型数组?

playground link

有相当浅的递归限制,实现模板文字类型的拉取请求,microsoft/TypeScript#40336 mentions that this is a pain point when you try to pull apart strings one character at a time. From this comment 由实现者:

Note that these types pretty quickly run afoul of the recursion depth limiter. That's an orthogonal issue I'm still thinking about.

所以目前我认为没有适用于任意长度字符串的解决方案。


就是说,您可以通过一次拆分更大的块来解决这个限制,这样您就不必递归那么多了。可能有一些最佳方法可以做到这一点,但我只是通过反复试验找到了将最长可拆分字符串从 ~14 增加到 ~80 个字符的方法:

type StringToChars<T extends string> =
  string extends T ? string[] :
  T extends `${infer C0}${infer C1}${infer C2}${infer C3}${infer C4}${infer C5}${infer R}` ? [C0, C1, C2, C3, C4, C5, ...StringToChars<R>] :
  T extends `${infer C0}${infer C1}${infer C2}${infer C3}${infer R}` ? [C0, C1, C2, C3, ...StringToChars<R>] :
  T extends `${infer C0}${infer R}` ? [C0, ...StringToChars<R>] : []


type WorksWith80 = StringToChars<'12345678911234567892123456789312345678941234567895123456789612345678971234567898'>;
type Len80 = WorksWith80['length']; // 80

type BreaksWith81 = StringToChars<'123456789112345678921234567893123456789412345678951234567896123456789712345678981'>;
type Len81 = BreaksWith81['length']; // number

你可以看到,我们不是只抓一个字符,而是尝试抓六个。如果失败,我们会选择四个,然后再回到一个。


你可能可以通过玩弄它来进一步突破限制,但我真的不明白这一点:这个算法是 丑陋的 并且似乎没有动力一堆关于递归限制的评论; TS 目前还没有为这些东西做好准备。希望@ahejlsberg 会在某个时候让这变得更好,我可以带着更好的消息回到这里。

Playground link to code