typescript 声明十六进制颜色类型时出错:“表达式产生的联合类型太复杂,无法表示(2590)”

iovurdzv  于 2023-10-22  发布在  TypeScript
关注(0)|答案(2)|浏览(225)

下面是我的类型声明:

  1. type Hex = '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'A'|'B'|'C'|'D'|'E'|'F'|'a'|'b'|'c'|'d'|'e'|'f';
  2. // ↓↓ Error: Expression produces a union type that is too complex to represent
  3. type HexColor = `#${Hex}${Hex}${Hex}${Hex}${Hex}${Hex}`;

typescript 版本:5.2.2.
Playground链接:
https://www.typescriptlang.org/play?ts=5.2.2#code/C4TwDgpgBAEhAeUC8UDkAGVAfVBGbqATAQMwEAsBArAQGwEDsBAHAQJwECCBAQgQMIEAIgQCiBAGIEAhgQBGBAMYEAJgQgEAZqgDcAKFCRYCfgHsANqYBOyKAAMAxABIA3nHgBfV+69uEvn29-IM87HSA
感谢@jcalz的回答,我根据他提供的答案做了一个小小的调整,这个Playground链接是最终的结果。

wydwbb8l

wydwbb8l1#

你的类型太复杂了。您正在要求TypeScript验证22^6可能性(113,379,904种可能状态)。

cbeh67ev

cbeh67ev2#

TypeScript中没有直接对应于HexColor的特定类型。工会类型最多只能容纳大约10万名成员。很少有可能在不遇到这个限制的情况下表示“所有可能组合的联合”。三个Hex字符的字符串可以表示为联合(223 <11,000),但四个或更多Hex字符的字符串不能表示为联合(224 > 200,000)。
在TypeScript中支持 * 正则表达式验证字符串类型 * 的功能请求由来已久;目前的开放问题是在microsoft/TypeScript#41160。如果实现了这一点,您应该能够以这种方式表示HexColor。但是现在这还不是语言的一部分,所以你需要另一种方法。
目前,您能得到的最接近的方法是给予特定的HexColor类型,而代之以编写一个genericHexColor<T>类型,它的作用类似于对字符串文字类型输入T的约束。目标是T extends HexColor<T>当且仅当T是有效的HexColor。此外,如果T * 不是 * 一个有效的HexColor,那么HexColor<T> * 是 * 一个有效的HexColor,它“接近”T,这样用户就可以得到一些可破译的错误消息。无论如何,您将使用HexColor<T>编写一个通用的帮助函数hexColor(),它只接受有效的十六进制颜色输入,并返回其输入。所以你应该写const y = hexColor("012345")而不是const y: HexColor = "012345"。所以一般的方法是

  1. type HexColor<T> = ⋯;
  2. const hexColor = <T extends HexColor<T>>(h: T) => h;
  3. const y = hexColor("012345"); // okay
  4. const n = hexColor("012GFE"); // error, "021GFE" is not "0210FE"

这里有一种写HexColor<T>的方法,它总是产生一个六个字符的字符串字面量,其字符是来自T的对应Hex字符(如果有),否则是"0"。所以"021GFE"变成了"0210FE"(因为"G"是inavlid),"ABCDE"变成了"ABCDE0"(因为字符串太短),"ABCDEFA"变成了"ABCDEF"(因为字符串太长):

  1. type HexColor<T extends string, L extends any[] = [], A extends string = ""> =
  2. L["length"] extends 6 ? A :
  3. T extends `${infer F}${infer R}` ? HexColor<R, [0, ...L], `${A}${F extends Hex ? F : 0}`>
  4. : HexColor<"", [0, ...L], `${A}0`>;

这是一个尾递归条件类型。L类型参数是一个累加器,它表示一个元组,该元组的长度与到目前为止处理的字符数相同。A类型参数是一个累加器,表示到目前为止的累加输出。(理想情况下,我们只使用A,但你不能直接问TypeScript字符串文字类型有多长,而你可以为元组类型做这件事。所以我用L["length"]代替A["length"]
如果L6,那么我们已经处理了6个字符,我们只返回累积输出A。否则,我们将T拆分为第一个字符F和字符串R的其余部分(使用模板文字类型和infer)并递归。下一步使用R代替T,一个比L长一个元素的元组代替L,对于累积输出,我们在A上附加一个字符。如果F是一个有效的Hex,那么我们附加F。否则,我们追加"0"。哦,如果我们不能拆分T,因为它是空的,那么我们仍然递归,但用一个空字符串""代替T,并用"0"作为附加字符。
您可以验证它的行为是否如上所述。
接下来,hexColor辅助函数与上面写的略有不同:

  1. const hexColor = <T extends string>(
  2. h: T extends HexColor<T> ? T : HexColor<T>) => h;

这是因为你不能写T extends HexColor<T>而不得到一个循环错误。因此,我们只需将T约束为string,然后将HexColor<T>应用到h类型中。同样,写<T extends string>(h: HexColor<T>) => h是很好的,但这并不能推断h的字符串文字类型。奇怪的T extends HexColor<T> ? T : HexColor<T>条件类型具有预期的效果,允许编译器推断T的字符串文字类型,将其与HexColor<T>进行比较,然后根据比较成功或失败计算为THexColor<T>。这是丑陋的/奇怪的,但这是我发现的唯一在实践中有效的方法。
好吧,让我们测试一下:

  1. const y1 = hexColor("012345"); // okay
  2. const y2 = hexColor("ABCDEF"); // okay
  3. const y3 = hexColor("00ff00"); // okay
  4. const n1 = hexColor("ABCDE"); // error!
  5. // Argument of type '"ABCDE"' is not assignable to parameter of type '"ABCDE0"'.
  6. const n2 = hexColor("ABCDEFA"); // error!
  7. // Argument of type '"ABCDEFA"' is not assignable to parameter of type '"ABCDEF"'.
  8. const n3 = hexColor("012GFE"); // error!
  9. // Argument of type '"012GFE"' is not assignable to parameter of type '"0120FE"'.
  10. const n4 = hexColor("whatever"); // error!
  11. // Argument of type '"whatever"' is not assignable to parameter of type '"00a0e0"'.

看起来不错。好的调用会成功,而坏的调用会失败,并显示一条错误消息,希望这些信息足以帮助开发人员发现问题。每个错误消息都提到“最接近”的有效输入。对于像"whatever"这样完全不好的输入,它可能不是那么明显("00a0e0"是有效的,并保留了"a""e"字符,而将其余的替换为"0",但谁知道一个困惑的开发人员是否会理解这一点),但希望它足够好。
Playground链接到代码

展开查看全部

相关问题