既然 #26063 已经被合并(顺便说一下,这很好),我在尝试使用另一种解决方案来处理 #1360 或 #25717。基本思路是将 NodeJS 回调风格的 API 的参数列表作为元组,并从其中生成两个其他类型:
1. 元组中最后一个元素的类型,可以通过以下方式实现:
// Helper type to drop the first item in a tuple, i.e. reduce its size by 1
type Drop1<T extends any[]> = ((...args: T) => void) extends ((a1: any, ...rest: infer R) => void) ? R : never;
type TakeLast<
T extends any[],
// Create a tuple which is 1 item shorter than T and determine its length
L1 extends number = Drop1<T>["length"],
// use that length to access the last index of T
> = T[L1];
// Example:
type Foo = TakeLast<[1, 5, 7, 8, 9, string]>; // string
2. 元组中除最后一个元素之外的所有元素的类型。
这个有点棘手,目前还不能完全实现。基本思路是再次使用简化后的元组,并将其与原始元组进行比较:
type MapTuples<T1 extends any[], T2 extends any[]> = { [K in keyof T1]: K extends keyof T2 ? T1[K] : never };
type Bar = MapTuples<[1, 2, 3], [4, 5]>; // [1, 2, never]
type Baz = MapTuples<[1, 2], [3, 4, 5]>; // [1, 2]
如您所见,当 T1
的长度大于 T2
时,多余的元素会被转换为 never
。相比之下,我们可以使用这种技巧从对象中删除属性,但在这里不起作用。因此,当我们尝试使用以下类型从参数列表中删除最后一个参数时
type DropLast<
T extends any[],
MinusOne extends any[] = Drop1<T>,
> = MapTuples<T, MinusOne>;
// DropLast<[1, 2, 3]> is [1, 2, never]
// Returns the params of a function as a tuple
type Params<F extends (...args: any[]) => void> = F extends ((...args: infer TFArgs) => any) ? TFArgs : never;
// creates a function type with one less argument than the given one
type DropLastArg<
F extends (...args: any[]) => void,
FArgs extends any[] = Params<F>,
RArgs extends any[] = DropLast<FArgs> // ** ERROR **
> = (...args: RArgs) => void;
最后一个参数仍然存在,但现在具有 never
类型
type F1 = DropLastArg<(arg1: number, arg2: string, arg3: boolean) => void>;
// F1 is (arg1: number, arg2: string, arg3: never) => void
此外,Map元组类型不再被视为元组 [Symbol.iterator()] is missing in type MapTuples<...>
,因此我们必须强制 RArgs 为一个
type ForceTuple<T> = T extends any[] ? T : any[];
type DropLastArg<
F extends (...args: any[]) => void,
FArgs extends any[] = Params<F>,
RArgs extends any[] = ForceTuple<DropLast<FArgs>>
> = (...args: RArgs) => void;
但现在 F1
具有 (...args: any[]) => void
类型,因为我们丢失了类型信息。
然而,通过进行一些更改,我们可以更接近期望的结果:
// notice how we now Map from T1 to T2
type MapTuples<T1 extends any[], T2 extends any[]> = { [K in keyof T1]: K extends keyof T2 ? T2[K] : never };
// MapTuples<[1, 2], [4, 5, 6]> is [4, 5]
type DropLast<
T extends any[],
// create a tuple that is 1 shorter than T
MinusOne extends any[] = Drop1<T>,
// and map the entries to the ones at the corresponding indizes in T
> = MapTuples<MinusOne, T>;
// DropLast<[1, 2, 3]> is [1, 2] :)
type F1 = DropLastArg<(arg1: number, arg2: string, arg3: boolean) => void>;
// F1 is (arg2: number, arg3: string) => void
请注意 F1 具有正确的参数类型,但名称少了一位!
建议
总之,我希望看到Map元组的一些改进,特别是:
- 从它们中删除项目的能力以及
- 复杂的Map元组仍然被识别为元组。
用例
一个很大的用例是为 NodeJS 回调风格的 API 提供类型提示,我差点就用这种方法来实现:
type Drop1<T extends any[]> = ((...args: T) => void) extends ((a1: any, ...rest: infer R) => void) ? R : never;
type TakeLast<
T extends any[],
// Create a tuple which is 1 item shorter than T and determine its length
L1 extends number = Drop1<T>["length"],
// use that length to access the last index of T
> = T[L1];
type MapTuples<T1 extends any[], T2 extends any[]> = { [K in keyof T1]: K extends keyof T2 ? T2[K] : never };
type DropLast<
T extends any[],
// create a tuple that is 1 shorter than T
MinusOne extends any[] = Drop1<T>,
// and keep only the entries with a corresponding index in T
> = MapTuples<MinusOne, T>;
type Params<F extends (...args: any[]) => void> = F extends ((...args: infer TFArgs) => any) ? TFArgs : never;
type ForceTuple<T> = T extends any[] ? T : any[];
type ForceFunction<T> = T extends ((...args: any[]) => any) ? T : ((...args: any[]) => any);
type Promisify<
F extends (...args: any[]) => void,
// Extract the argument types
FArgs extends any[] = Params<F>,
// Infer the arguments for the promisified version
PromiseArgs extends any[] = ForceTuple<DropLast<FArgs>>,
// Parse the callback args
CallbackArgs extends any[] = Params<ForceFunction<TakeLast<FArgs>>>,
CallbackLength = LengthOf<CallbackArgs>,
TError = CallbackArgs[0],
// And extract the return value
TResult = 1 extends CallbackLength ? void : CallbackArgs[1]
> = (...args: PromiseArgs) => Promise<TResult>;
示例
type F1 = (arg1: number, arg2: string, c: (err: Error, ret: boolean) => void) => void;
type F1Async = Promisify<F1>;
// F1Async is (arg2: number, c: string) => Promise<boolean>; (YAY!)
检查清单
我的建议满足以下指导原则:
- 这不会对现有的 TypeScript / JavaScript 代码造成破坏性的变化
- 这不会改变现有 JavaScript 代码的运行时行为
- 这可以在不根据表达式的类型发出不同的 JS 的情况下实现
- 这不是一个运行时特性(例如新的表达式级语法)
2条答案
按热度按时间qlvxas9a1#
@AlCalzone
DropLast<[1 | undefined, 2?]>
缩小为[(1 | undefined)?]
而不是[1 | undefined]
pqwbnv8z2#
当我在去年8月编写这些类型时,它们曾经起作用。但是由于它们涉及许多笨拙的解决方法,因此很可能它们不再起作用了。