如何将字符串的联合重新映射到数字的联合?



From type:

type BeforeType = "1" | "2"

我想要一个类型:

type AfterType = 1 | 2

TypeScript目前还不支持将表示数字的字符串文字类型转换为相应的数字文字的类型函数(姑且叫它StringToNumber<T>)。我不知道GitHub中是否有任何开放的问题,但是microsoft/TypeScript#26382请求对数字文字进行执行操作的能力,这是相当接近的(至少如果你能做到这一点,你可能会自己写一个体面的StringToNumber<T>)。

有各种可能的解决方法,但它们要么只适用于硬编码的数字列表,要么至多可以使用元组类型操作来表示非负整数。当然,如果你看的是"1""2"这样的数字,这些都是不值得做的;任何用户编写的StringToNumber<T>的实现都将比自己编写12更复杂或更脆弱。

例如,下面是使用递归条件类型使用元组计算StringToNumber<T>的相当简单的实现:
type StringToNumber<T extends `${number}`, R extends number[] = []> =
T extends keyof R ? R[T] : StringToNumber<T, [...R, R['length']]>

它完全适用于"1" | "2",没有问题:

type NewAfterType = StringToNumber<"1" | "2">
// type NewAfterType = 1 | 2

但是对于TS4.4,它会跳出大于~40的数字:

type Oops = StringToNumber<"100"> // error TS4.4
// Type instantiation is excessively deep and possibly infinite

对于负数或非整数会发生不好的事情:

type Blah = StringToNumber<"-2"> // error 
// Type instantiation is excessively deep and possibly infinite
type AlsoBlah = StringToNumber<"0.5"> // error
// Type instantiation is excessively deep and possibly infinite

我不认为这是值得的,除非你的用例是专门处理小的非负整数,对于这些你可以写一个查找表:

type Nums = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 44, 45, 46, 47, 48, 49
];
type StringToNumber<T extends Exclude<keyof Nums, keyof any[]>> = Nums[T]

所以StringToNumber<T>没有提供,也不容易实现。


也就是说,TypeScript确实有能力做相反的事情(让我们称之为NumberToString<T>),你可以将数字文字类型转换为相应的字符串文字类型。该实现使用模板文字类型:

type NumberToString<T extends number> = `${T}`;

所以你可以从AfterType生成BeforeType:

type NewBeforeType = NumberToString<AfterType>;
// type NewBeforeType = "1" | "2"

根据用例,您可能能够重构以数字开始并以字符串结束。或者您可以手动写出BeforeTypeAfterType,并让编译器通过比较BeforeTypeNumberToString<AfterType>来检查AfterType是否对应于BeforeType。例如:

declare var checkConversion: BeforeType;
declare var checkConversion: NewBeforeType;
// -------> ~~~~~~~~~~~~~~~
// if there's an error here, it means you made a mistake

Playground链接到代码

最新更新