有没有办法在打字稿的类型级别将两个数字加在一起?
Is there a way to add two numbers together on the type level in typescript?
假设我有以下类型
type One = 1;
type SmallEvens = 0 | 2 | 4 | 6 | 8 | 10;
type Three = 3;
是否有某种类型 Add<T, U>
我可以定义在类型级别 添加数字 像这样?
type SmallOdds = Add<One, SmallEvens>; // same as `1 | 3 | 5 | 7 | 9 | 11`
type Four = Add<One, Three> // same as `4`
或者,我是否应该寻找数字的替代表示法来实现这种效果?但是,能够转换为 extends number
的内容以便我可以将其用于元组索引将是一个很大的优势。
经过一些简单的搜索后,我发现了这篇引人入胜的文章 - Implementing Arithmetic Within TypeScript’s Type System - 它确实展示了一种使用一些前沿功能来实现你正在尝试的目标的方法。
我们可以创建一个将两个数字相加的类型,如下所示:
type Length<T extends any[]> =
T extends { length: infer L } ? L : never;
type BuildTuple<L extends number, T extends any[] = []> =
T extends { length: L } ? T : BuildTuple<L, [...T, any]>;
type Add<A extends number, B extends number> =
Length<[...BuildTuple<A>, ...BuildTuple<B>]>;
type Seven = Add<3, 4> // 7
我建议阅读这篇文章以了解作者的解释(以及更多算术类型)。然而,简而言之:
类型Length
推断传递给它的数组类型的长度属性。如果该数组类型是元组,它将提供实际长度而不仅仅是 number
.
Length<[number, number, string]> // 3
BuildTuple
类型将创建一个长度为 L
的元组类型,填充类型为 any
。这通过使用扩展运算符递归地增加元组的长度直到它匹配 length: L
的条件来工作,此时它将被返回。
BuildTuple<3> // [any, any, any]
Add
类型现在应该是不言自明的了。我们将元组构建为提供的两个数字的两个长度,然后将它们扩展到一个新的元组并获得组合长度。
虽然作者不包括添加到联合类型,但我们自己可以使用映射类型相当轻松地做到这一点:
type MappedAdd<A extends number, B extends number> = {
[key in B]: Add<A, key>
}[B]
type One = 1;
type SmallEvens = 0 | 2 | 4 | 6 | 8 | 10;
type SmallOdds = MappedAdd<One, SmallEvens>; // 1 | 3 | 5 | 7 | 9 | 11
对于这个用例,效果很好。然而,最终元组创建类型的递归将变得太深,将其限制为 45 以下的数字(或映射版本为 44):
Add<1, 45> // Type instantiation is excessively deep and possibly infinite.
假设我有以下类型
type One = 1;
type SmallEvens = 0 | 2 | 4 | 6 | 8 | 10;
type Three = 3;
是否有某种类型 Add<T, U>
我可以定义在类型级别 添加数字 像这样?
type SmallOdds = Add<One, SmallEvens>; // same as `1 | 3 | 5 | 7 | 9 | 11`
type Four = Add<One, Three> // same as `4`
或者,我是否应该寻找数字的替代表示法来实现这种效果?但是,能够转换为 extends number
的内容以便我可以将其用于元组索引将是一个很大的优势。
经过一些简单的搜索后,我发现了这篇引人入胜的文章 - Implementing Arithmetic Within TypeScript’s Type System - 它确实展示了一种使用一些前沿功能来实现你正在尝试的目标的方法。
我们可以创建一个将两个数字相加的类型,如下所示:
type Length<T extends any[]> =
T extends { length: infer L } ? L : never;
type BuildTuple<L extends number, T extends any[] = []> =
T extends { length: L } ? T : BuildTuple<L, [...T, any]>;
type Add<A extends number, B extends number> =
Length<[...BuildTuple<A>, ...BuildTuple<B>]>;
type Seven = Add<3, 4> // 7
我建议阅读这篇文章以了解作者的解释(以及更多算术类型)。然而,简而言之:
类型Length
推断传递给它的数组类型的长度属性。如果该数组类型是元组,它将提供实际长度而不仅仅是 number
.
Length<[number, number, string]> // 3
BuildTuple
类型将创建一个长度为 L
的元组类型,填充类型为 any
。这通过使用扩展运算符递归地增加元组的长度直到它匹配 length: L
的条件来工作,此时它将被返回。
BuildTuple<3> // [any, any, any]
Add
类型现在应该是不言自明的了。我们将元组构建为提供的两个数字的两个长度,然后将它们扩展到一个新的元组并获得组合长度。
虽然作者不包括添加到联合类型,但我们自己可以使用映射类型相当轻松地做到这一点:
type MappedAdd<A extends number, B extends number> = {
[key in B]: Add<A, key>
}[B]
type One = 1;
type SmallEvens = 0 | 2 | 4 | 6 | 8 | 10;
type SmallOdds = MappedAdd<One, SmallEvens>; // 1 | 3 | 5 | 7 | 9 | 11
对于这个用例,效果很好。然而,最终元组创建类型的递归将变得太深,将其限制为 45 以下的数字(或映射版本为 44):
Add<1, 45> // Type instantiation is excessively deep and possibly infinite.