Typescript 中逗号分隔字符串的真正递归模板文字

cef*_*efn 9 csv sorting recursion typescript template-literals

我正在尝试为包含逗号分隔值的字符串定义 Typescript 模板文字。我能让这个定义真正递归和通用吗?

请参阅此打字稿游乐场来尝试该案例。

每个逗号分隔的值代表一个排序顺序,例如height asc。该字符串应定义一个顺序(包括一级、二级、三级等),该顺序可以根据有效字段名称和两个可能的排序"asc"和 的并集包含无限多个排序级别"desc",并按照示例代码中的示例以逗号分隔。

下面的实现最多处理 4 个排序顺序,但案例 5 表明它并不是真正的递归。当前扩展 (2x2) 的数量最多仅包含 4 个可能的值,因此幸运地处理了我尝试的初始情况。

const FIELD_NAMES = [
  "height",
  "width",
  "depth",
  "time",
  "amaze",
] as const;

const SORT_ORDERS = [
  "asc",
  "desc",
] as const;

type Field = typeof FIELD_NAMES[number];
type Order = typeof SORT_ORDERS[number];

type FieldOrder = `${Field} ${Order}`
type Separated<S extends string> = `${S}${""|`, ${S}`}`;
type Sort = Separated<Separated<FieldOrder>>;

/** SUCCESS CASES */
const sort1:Sort = "height asc"; //compiles
const sort2:Sort = "height asc, depth desc"; //compiles
const sort3:Sort = "height asc, height asc, height asc"; //compiles
const sort4:Sort = "height asc, width asc, depth desc, time asc"; //compiles
const sort5:Sort = "height asc, width asc, depth desc, time asc, amaze desc"; //SHOULD compile but doesn't

/** FAILURE CASES */
const sort6:Sort = "height"; //doesn't compile 
const sort7:Sort = "height asc,"; //doesn't compile
const sort8:Sort = ""; //doesn't compile
Run Code Online (Sandbox Code Playgroud)

我无法再增加此模板文字的“数量”,因为尝试执行如下所示的 2x2x2 会导致Expression produces a union type that is too complex to represent

type Sort = Separated<Separated<Separated<FieldOrder>>>;
Run Code Online (Sandbox Code Playgroud)

是否可以定义一个模板文字来处理一般情况?

jca*_*alz 15

正如您所看到的,您正在创建的模板文字类型很快就会耗尽编译器表示联合的能力。如果您阅读实现模板文字类型的拉取请求,您会发现联合类型最多只能有 100,000 个元素。因此,您最多只能Sort接受 4 个逗号分隔值(这将需要大约 11,110 个成员)。你当然不能让它接受任意数字,因为这意味着Sort需要是一个无限联合,而无穷大比 100,000 稍大。因此,我们必须放弃表示Sort为特定联合类型这一不可能完成的任务。


一般来说,在这种情况下我的方法是从特定类型切换到充当递归约束的泛型类型。所以我们有. 如果是有效的排序字符串类型,则相当于。否则,将是一些合理的候选者(或这些候选者的并集),其中“接近” 。SortValidSort<T>TValidSort<T>TValidSort<T>SortT

这意味着您打算编写的任何地方Sort现在都需要编写ValidSort<T>一些泛型类型参数并将其添加到适当的范围。此外,除非您想强迫某人编写const s: ValidSort<"height asc"> = "height asc";,否则您将需要调用一个辅助函数,例如asSort()检查其输入并推断类型。意思是你得到了const s = asSort("height asc");

它可能并不完美,但这可能是我们能做的最好的。


我们看一下定义:

type ValidSort<T extends string> = T extends FieldOrder ? T :
  T extends `${FieldOrder}, ${infer R}` ? T extends `${infer F}, ${R}` ?
  `${F}, ${ValidSort<R>}` : never : FieldOrder;

const asSort = <T extends string>(t: T extends ValidSort<T> ? T : ValidSort<T>) => t;
Run Code Online (Sandbox Code Playgroud)

ValidSort<T>是一种递归条件类型,它检查字符串类型T以查看它是否是 aFieldOrder或以 a 开头,FieldOrder后跟逗号和空格的字符串。如果它是 a FieldOrder,那么我们就有了一个有效的排序字符串,我们只需返回它即可。如果它以 开头FieldOrder,那么我们递归地检查字符串的其余部分。否则,我们有一个无效的排序字符串,并且我们返回FieldOrder

让我们看看它的实际效果。您的成功案例现在都按预期工作:

/** SUCCESS CASES */
const sort1 = asSort("height asc"); //compiles
const sort2 = asSort("height asc, depth desc"); //compiles
const sort3 = asSort("height asc, height asc, height asc"); //compiles
const sort4 = asSort("height asc, width asc, depth desc, time asc"); //compiles
const sort5 = asSort(
  "height asc, width asc, depth desc, time asc, amaze desc"); //compiles
Run Code Online (Sandbox Code Playgroud)

并且失败案例失败,并显示错误消息,显示您应该使用的“足够接近”的类型:

/** FAILURE CASES */
const sort6 = asSort("height"); // error!
/* Argument of type '"height"' is not assignable to parameter of type 
'"height asc" | "height desc" | "width asc" | "width desc" | "depth asc" | 
"depth desc" | "time asc" | "time desc" | "amaze asc" | "amaze desc"'. */

const sort7 = asSort("height asc,"); // error!
/* Argument of type '"height asc,"' is not assignable to parameter of type 
'"height asc" | "height desc" | "width asc" | "width desc" | "depth asc" | 
"depth desc" | "time asc" | "time desc" | "amaze asc" | "amaze desc"'. */

const sort8 = asSort(""); // error!
/* Argument of type '""' is not assignable to parameter of type 
'"height asc" | "height desc" | "width asc" | "width desc" | "depth asc" | 
"depth desc" | "time asc" | "time desc" | "amaze asc" | "amaze desc"'. */

const sort9 = asSort("height asc, death desc"); // error!
/* Argument of type '"height asc, death desc"' is not assignable to parameter of type 
'"height asc, depth desc" | "height asc, height asc" | "height asc, time asc" |
 "height asc, amaze desc" | "height asc, height desc" | "height asc, width asc" | 
 "height asc, width desc" | "height asc, depth asc" | "height asc, time desc" | 
 "height asc, amaze asc"'. */
Run Code Online (Sandbox Code Playgroud)

我添加它sort9是为了向您展示错误消息如何不仅向您显示,还向您显示以 开头的FieldOrder字符串。"height asc, "FieldOrder

Playground 代码链接