Nur*_*yev 4 typescript recursive-type typescript-typings
我原来的问题是这样的。
我做了以下类型,但由于循环引用错误它不起作用,我不知道如何解决它:
type Increment<T extends number, Tuple extends any[] = [any]> =
T extends 0 ? 1 :
T extends 1 ? 2 :
T extends TupleUnshift<any, Tuple> ?
TupleUnshift<any, TupleUnshift<any, Tuple>>['length'] :
Increment<T, TupleUnshift<any, TupleUnshift<any, Tuple>>>
Run Code Online (Sandbox Code Playgroud)
最后它应该像这样工作:
type five = 5
type six = Increment<five> // 6
Run Code Online (Sandbox Code Playgroud)
PSTupleUnshift来自这里。
欢迎回来!TypeScript 4.1 引入了递归条件类型,它与诸如可变元组类型之类的东西一起使得通过递归执行“向非负整数加一”成为可能。它实际上表现得很好,但我仍然不推荐它用于生产环境,原因我将很快介绍。
首先,我会恬不知耻地复制借用的技术此评论通过@lazytype在Microsoft /打字稿#26223这使得任何非负整数长度的元组没有砸入递归限制在约深度23.打破做到这一点将整数转换为 2 的不同幂的总和(即,使用其二进制表示)并连接这些长度的元组。可变元组类型可以很容易地将元组 ( [...T, ...T])的长度加倍,因此该技术支持长度为数千和数万的元组:
type BuildPowersOf2LengthArrays<N extends number, R extends never[][]> =
R[0][N] extends never ? R : BuildPowersOf2LengthArrays<N, [[...R[0], ...R[0]], ...R]>;
type ConcatLargestUntilDone<N extends number, R extends never[][], B extends never[]> =
B["length"] extends N ? B : [...R[0], ...B][N] extends never
? ConcatLargestUntilDone<N, R extends [R[0], ...infer U] ? U extends never[][] ? U : never : never, B>
: ConcatLargestUntilDone<N, R extends [R[0], ...infer U] ? U extends never[][] ? U : never : never, [...R[0], ...B]>;
type Replace<R extends any[], T> = { [K in keyof R]: T }
type TupleOf<T, N extends number> = number extends N ? T[] : {
[K in N]:
BuildPowersOf2LengthArrays<K, [[never]]> extends infer U ? U extends never[][]
? Replace<ConcatLargestUntilDone<K, U, []>, T> : never : never;
}[N]
Run Code Online (Sandbox Code Playgroud)
那么Increment你想要的类型就是这样:
type Increment<N extends number> = [0, ...TupleOf<0, N>]['length'];
Run Code Online (Sandbox Code Playgroud)
它创建了一个长度N为 0的元组(不管你在那里使用什么),在0它前面加上一个单一的,并得到它的长度。
让我们看看它的实际效果:
type Seven = Increment<6>; // 7
type Sixteen = Increment<15>; // 16
type OneHundredOne = Increment<100>; // 101
type OneThousand = Increment<999>; // 1000
type SixOrElevent = Increment<5 | 10>; // 6 | 11
type Numbah = Increment<number>; // number
Run Code Online (Sandbox Code Playgroud)
好的!这看起来像我们想要的,我想。现在对于不太好的部分以及我不推荐它的原因:
// Don't do this
type Kablooey = Increment<3.14> // Loading...
Run Code Online (Sandbox Code Playgroud)
那会导致编译器心脏病发作;递归元组构建器永远不会达到它的目标,因为无论它生成多大的元组, index 处都没有元素3.14。
可以修复吗?当然。TupleOf如果数字的字符串表示中有小数点,我们可以在类型中添加一个额外的保护(对模板文字类型大喊大叫!):
type TupleOf<T, N extends number> = number extends N ? T[] :
`${N}` extends `${infer X}.${infer Y}` ? T[] : {
[K in N]:
BuildPowersOf2LengthArrays<K, [[never]]> extends infer U ? U extends never[][]
? Replace<ConcatLargestUntilDone<K, U, []>, T> : never : never;
}[N]
Run Code Online (Sandbox Code Playgroud)
现在3.14导致number,这不是4.14,但至少是合理的,而不是编译器爆炸:
type AlsoNumber = Increment<3.14> // number
Run Code Online (Sandbox Code Playgroud)
所以你去了;它运行良好。
不过,我仍然无法告诉任何人“请在您的生产环境中使用它”。它的潜在脆弱性太大了。看看上面的拜占庭类型,你有多确定没有一些简单的边缘情况会导致你的编译器崩溃?你想负责修补这些东西只是为了让编译器给一个数字加一吗?
相反,我仍然建议进行简单的查找,直到并且除非 TypeScript 按照microsoft/TypeScript#15645和/或microsoft/TypeScript#26382 中的要求对数字文字实现算术。
我认为在另一个问题和评论中,我说您可以尝试递归地执行此操作,但是编译器即使您对定义感到满意,也会在相对较浅的深度上放弃。让我们看看这里:
interface Reduction<Base, In> {
0: Base
1: In
}
type Reduce<T, R extends Reduction<any, any>, Base =[]> =
R[[T] extends [Base] ? 0 : 1]
type Cons<H, T extends any[]> = ((h: H, ...t: T) => void) extends
((...c: infer C) => void) ? C : never;
interface TupleOfIncrementedLength<N extends number, Tuple extends any[]=[]>
extends Reduction<Tuple, Reduce<Tuple['length'],
TupleOfIncrementedLength<N, Cons<any, Tuple>>, N
>> { }
type Increment<N extends number> = TupleOfIncrementedLength<N>[1] extends
{ length: infer M } ? M : never;
Run Code Online (Sandbox Code Playgroud)
编辑:你要求解释,所以这里是:
首先,定义在休息/传播位置和条件类型中Cons<H, T>使用元组来获取头部类型和元组尾部类型,并返回一个新的元组,其中头部已被添加到尾部。(名称“cons”来自 Lisp 和后来的 Haskell 中的列表操作。)所以计算结果为.HTCons<"a", ["b","c"]>["a","b","c"]
作为背景,TypeScript 通常会阻止您使用循环类型。你可以通过使用一些条件类型来推迟一些执行,像这样绕过后门:
type RecursiveThing<T> =
{ 0: BaseCase, 1: RecursiveThing<...T...> }[Test<...T...> extends BaseCaseTest ? 0 : 1]
Run Code Online (Sandbox Code Playgroud)
这应该计算为BaseCaseor RecursiveThing<...T...>,但是索引访问中的条件类型被推迟,因此编译器没有意识到它最终会成为循环引用。很不幸,这有非常不好导致编译走出低谷的无限类型和经常陷入困境,当你开始在实践中使用这些东西的副作用。例如,您可以这样定义TupleOfIncrementedLength<>:
type TupleOfIncrementedLength<N extends number, Tuple extends any[]=[]> = {
0: Cons<any, Tuple>,
1: TupleOfIncrementedLength<N, Cons<any, Tuple>>
}[Tuple['length'] extends N ? 0 : 1]
Run Code Online (Sandbox Code Playgroud)
它可以工作,甚至可以N达到 40 或 50 左右。但是如果你把它放在一个库中并开始使用它,你可能会发现你的编译时间变得非常长,甚至导致编译器崩溃。它不一致,所以我无法在这里轻松生成示例,但它足以让我避免它。这个问题最终可能会得到解决。但现在,我会按照建议的@ahejlsberg(为打字稿首席架构师):
这很聪明,但它肯定会使事情远远超出其预期用途。虽然它可能适用于小例子,但它的扩展性会很差。解析那些深度递归类型会消耗大量时间和资源,并且将来可能会与我们在检查器中使用的递归调控器发生冲突。
不要这样做!
输入@strax,他发现由于interface声明不会像声明那样急切地求值type(因为types 只是别名,编译器会尝试对它们求值),如果您可以interface以正确的方式扩展 an并结合条件类型上面的技巧,编译器不应该陷入困境。我的实验证实了这一点,但我仍然不相信......可能会有一个反例,只有当你尝试编写这些东西时才会出现。
无论如何,上面的TupleOfIncrementedLength类型和Reduction和Reduce之前的“naïve”版本的工作方式大致相同,只是它是一个interface. 不写十页我真的无法把它拆开,我不想这样做,对不起。实际输出是 a ,Reduction其1属性具有我们关心的元组。
在此之后,Increment在来定义的TupleOfIncrementedLength通过获取1财产并提取其length(我不使用纯索引访问,因为编译器不能推断出TupleOfIncrementedLength<N>[1]是一个数组类型。幸运的是有条件的类型推断为我们节省了)。
我不知道详细介绍它的工作原理对我来说太有用了。 可以说它不断增加元组的长度,直到它的长度比N参数大 1,然后返回该元组的长度。它确实有效,对于小N:
type Seven = Increment<6>; // 7
type Sixteen = Increment<15>; // 16
Run Code Online (Sandbox Code Playgroud)
但是,至少在我的编译器(版本 3.3.0-dev.20190117)上,会发生这种情况:
type TwentyThree = Increment<22>; // 23
type TwentyWhaaaa = Increment<23>; // {}
type Whaaaaaaaaaa = Increment<100>; // {}
Run Code Online (Sandbox Code Playgroud)
此外,您对工会和number以下内容感到有些奇怪:
type WhatDoYouThink = Increment<5 | 10>; // 6
type AndThis = Increment<number>; // 1
Run Code Online (Sandbox Code Playgroud)
您可能可以使用条件类型来修复这两种行为,但这感觉就像您正在救助一艘正在沉没的船。
如果上述超级复杂和脆弱的方法在 23 处完全失效,您不妨使用我在另一个问题的答案中展示的硬编码输出列表。对于任何想在一个地方看到答案的人来说,它是:
type Increment<N extends number> = [
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,50,51,52,53,54, // as far as you need
...number[] // bail out with number
][N]
Run Code Online (Sandbox Code Playgroud)
这相对简单,适用于更高的数字:
type Seven = Increment<6>; // 7
type Sixteen = Increment<15>; // 16
type TwentyThree = Increment<22>; // 23
type TwentyFour = Increment<23>; // 24
type FiftyFour = Increment<53>; // 54
Run Code Online (Sandbox Code Playgroud)
失败时通常更优雅,并在已知位置失败:
type NotFiftyFiveButNumber = Increment<54>; // number
type NotOneHundredOneButNumber = Increment<100>; // number
Run Code Online (Sandbox Code Playgroud)
并且在上述“奇怪”的情况下自然会做得更好
type WhatDoYouThink = Increment<5 | 10>; // 6 | 11
type AndThis = Increment<number>; // number
Run Code Online (Sandbox Code Playgroud)
所以,总而言之,在我看来,递归类型比它们在这里的价值更麻烦。
好的,希望再次帮助。祝你好运!
| 归档时间: |
|
| 查看次数: |
919 次 |
| 最近记录: |