TypeScript 进一步改进Map类型对元组和数组的支持(几乎完成了Promisify< T>的内部实现!)

acruukt9  于 9个月前  发布在  TypeScript
关注(0)|答案(2)|浏览(108)

既然 #26063 已经被合并(顺便说一下,这很好),我在尝试使用另一种解决方案来处理 #1360#25717。基本思路是将 NodeJS 回调风格的 API 的参数列表作为元组,并从其中生成两个其他类型:

1. 元组中最后一个元素的类型,可以通过以下方式实现:

  1. // Helper type to drop the first item in a tuple, i.e. reduce its size by 1
  2. type Drop1<T extends any[]> = ((...args: T) => void) extends ((a1: any, ...rest: infer R) => void) ? R : never;
  3. type TakeLast<
  4. T extends any[],
  5. // Create a tuple which is 1 item shorter than T and determine its length
  6. L1 extends number = Drop1<T>["length"],
  7. // use that length to access the last index of T
  8. > = T[L1];
  9. // Example:
  10. type Foo = TakeLast<[1, 5, 7, 8, 9, string]>; // string

2. 元组中除最后一个元素之外的所有元素的类型。

这个有点棘手,目前还不能完全实现。基本思路是再次使用简化后的元组,并将其与原始元组进行比较:

  1. type MapTuples<T1 extends any[], T2 extends any[]> = { [K in keyof T1]: K extends keyof T2 ? T1[K] : never };
  2. type Bar = MapTuples<[1, 2, 3], [4, 5]>; // [1, 2, never]
  3. type Baz = MapTuples<[1, 2], [3, 4, 5]>; // [1, 2]

如您所见,当 T1 的长度大于 T2 时,多余的元素会被转换为 never。相比之下,我们可以使用这种技巧从对象中删除属性,但在这里不起作用。因此,当我们尝试使用以下类型从参数列表中删除最后一个参数时

  1. type DropLast<
  2. T extends any[],
  3. MinusOne extends any[] = Drop1<T>,
  4. > = MapTuples<T, MinusOne>;
  5. // DropLast<[1, 2, 3]> is [1, 2, never]
  6. // Returns the params of a function as a tuple
  7. type Params<F extends (...args: any[]) => void> = F extends ((...args: infer TFArgs) => any) ? TFArgs : never;
  8. // creates a function type with one less argument than the given one
  9. type DropLastArg<
  10. F extends (...args: any[]) => void,
  11. FArgs extends any[] = Params<F>,
  12. RArgs extends any[] = DropLast<FArgs> // ** ERROR **
  13. > = (...args: RArgs) => void;

最后一个参数仍然存在,但现在具有 never 类型

  1. type F1 = DropLastArg<(arg1: number, arg2: string, arg3: boolean) => void>;
  2. // F1 is (arg1: number, arg2: string, arg3: never) => void

此外,Map元组类型不再被视为元组 [Symbol.iterator()] is missing in type MapTuples<...> ,因此我们必须强制 RArgs 为一个

  1. type ForceTuple<T> = T extends any[] ? T : any[];
  2. type DropLastArg<
  3. F extends (...args: any[]) => void,
  4. FArgs extends any[] = Params<F>,
  5. RArgs extends any[] = ForceTuple<DropLast<FArgs>>
  6. > = (...args: RArgs) => void;

但现在 F1 具有 (...args: any[]) => void 类型,因为我们丢失了类型信息。
然而,通过进行一些更改,我们可以更接近期望的结果:

  1. // notice how we now Map from T1 to T2
  2. type MapTuples<T1 extends any[], T2 extends any[]> = { [K in keyof T1]: K extends keyof T2 ? T2[K] : never };
  3. // MapTuples<[1, 2], [4, 5, 6]> is [4, 5]
  4. type DropLast<
  5. T extends any[],
  6. // create a tuple that is 1 shorter than T
  7. MinusOne extends any[] = Drop1<T>,
  8. // and map the entries to the ones at the corresponding indizes in T
  9. > = MapTuples<MinusOne, T>;
  10. // DropLast<[1, 2, 3]> is [1, 2] :)
  11. type F1 = DropLastArg<(arg1: number, arg2: string, arg3: boolean) => void>;
  12. // F1 is (arg2: number, arg3: string) => void

请注意 F1 具有正确的参数类型,但名称少了一位!

建议

总之,我希望看到Map元组的一些改进,特别是:

  • 从它们中删除项目的能力以及
  • 复杂的Map元组仍然被识别为元组。

用例

一个很大的用例是为 NodeJS 回调风格的 API 提供类型提示,我差点就用这种方法来实现:

  1. type Drop1<T extends any[]> = ((...args: T) => void) extends ((a1: any, ...rest: infer R) => void) ? R : never;
  2. type TakeLast<
  3. T extends any[],
  4. // Create a tuple which is 1 item shorter than T and determine its length
  5. L1 extends number = Drop1<T>["length"],
  6. // use that length to access the last index of T
  7. > = T[L1];
  8. type MapTuples<T1 extends any[], T2 extends any[]> = { [K in keyof T1]: K extends keyof T2 ? T2[K] : never };
  9. type DropLast<
  10. T extends any[],
  11. // create a tuple that is 1 shorter than T
  12. MinusOne extends any[] = Drop1<T>,
  13. // and keep only the entries with a corresponding index in T
  14. > = MapTuples<MinusOne, T>;
  15. type Params<F extends (...args: any[]) => void> = F extends ((...args: infer TFArgs) => any) ? TFArgs : never;
  16. type ForceTuple<T> = T extends any[] ? T : any[];
  17. type ForceFunction<T> = T extends ((...args: any[]) => any) ? T : ((...args: any[]) => any);
  18. type Promisify<
  19. F extends (...args: any[]) => void,
  20. // Extract the argument types
  21. FArgs extends any[] = Params<F>,
  22. // Infer the arguments for the promisified version
  23. PromiseArgs extends any[] = ForceTuple<DropLast<FArgs>>,
  24. // Parse the callback args
  25. CallbackArgs extends any[] = Params<ForceFunction<TakeLast<FArgs>>>,
  26. CallbackLength = LengthOf<CallbackArgs>,
  27. TError = CallbackArgs[0],
  28. // And extract the return value
  29. TResult = 1 extends CallbackLength ? void : CallbackArgs[1]
  30. > = (...args: PromiseArgs) => Promise<TResult>;

示例

  1. type F1 = (arg1: number, arg2: string, c: (err: Error, ret: boolean) => void) => void;
  2. type F1Async = Promisify<F1>;
  3. // F1Async is (arg2: number, c: string) => Promise<boolean>; (YAY!)

检查清单

我的建议满足以下指导原则:

  • 这不会对现有的 TypeScript / JavaScript 代码造成破坏性的变化
  • 这不会改变现有 JavaScript 代码的运行时行为
  • 这可以在不根据表达式的类型发出不同的 JS 的情况下实现
  • 这不是一个运行时特性(例如新的表达式级语法)
qlvxas9a

qlvxas9a1#

@AlCalzone DropLast<[1 | undefined, 2?]> 缩小为 [(1 | undefined)?] 而不是 [1 | undefined]

pqwbnv8z

pqwbnv8z2#

当我在去年8月编写这些类型时,它们曾经起作用。但是由于它们涉及许多笨拙的解决方法,因此很可能它们不再起作用了。

相关问题