这个问题以前肯定有人问过,我几乎可以肯定。然而:
我在代码中常用的东西是用于函数参数的扩展运算符,允许我接收可变长度的参数数组。我现在要做的是为一个函数创建 TS 类型定义,其中 arg1
类型取决于 arg2
类型,而 arg2
取决于 arg3
,依此类推。
非常像 lodash https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/lodash/common/util.d.ts#L209
flow<R2, R3, R4, R5, R6, R7>(f2: (a: ReturnType<T>) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6, f7: (a: R6) => R7): Function<(...args: Parameters<T>) => R7>;
lodash 方法显然非常有限,并且在 8 个参数时也最大化(在本例中)。这很好,我可以接受,但是,在 2021 年有更好、更递归的方法吗?一种对(理论上)infinite
个参数执行相同操作的方法?
请随时结束问题并指出现有答案(如果存在且是最新的)。
答案 0 :(得分:4)
在条件类型中进行推断,这是可能的(尽管有点难看):
// convenience alias
type Func = (...args: any[]) => any;
// check arguments in reverse
// since typescript doesn't like deconstructing arrays into init/last
type CheckArgsRev<T extends Func[]> =
// get first two elements
T extends [infer H1, infer H2, ...infer R]
// typescript loses typings on the inferred types
// so this gains them back
? H1 extends Func ? H2 extends Func ? R extends Func[]
// actual check
// ensures parameters of next argument extends return type of previous
// you can substitute this with whatever check you want to add
// just know that H1 is the current argument type and H2 is the previous
// also if you change this to work with non-functions then change Func to an appropriate type
// like unknown to work with all types
? Parameters<H1> extends [ReturnType<H2>]
// it was a match, recurse onto the tail
? [H1, ...CheckArgsRev<[H2, ...R]>]
: never // invalid type, become never for error
: never : never : never // should never happen
// base case, 0 or 1 elements should always pass
: T;
// reverse a tuple type
type Reverse<T extends unknown[]> =
T extends [infer H, ...infer R]
? [...Reverse<R>, H]
: [];
// check args not in reverse by reversing twice
type CheckArgs<T extends Func[]> = Reverse<CheckArgsRev<Reverse<T>>>;
// make sure the argument passes the check
function flow<T extends Func[]>(...args: T & CheckArgs<T>) {
console.log(args);
}
// this is invalid (since number cannot flow into a string)
flow((x: string) => parseInt(x, 10), (x: string) => x + "1");
// this is valid (number flows into number)
flow((x: string) => parseInt(x, 10), (x: number) => x + 1);
答案 1 :(得分:1)
Aplet's answer 很棒!但是当您传递错误的参数时,它会放弃有关函数数组的类型信息。当错误实际上源自 Argument of type '(x: string) => number' is not assignable to parameter of type 'never'.
时仅显示 (x: string) => string
的错误不是很有帮助。
不幸的是,似乎没有一种可靠的方法可以做到这一点,但我仍然想分享我的 2 美分-
type AnyFunc = (a: any) => any;
// Given a tuple of functions - deduce how much of it is flowable
type FlowFrom<T extends readonly AnyFunc[]> = T extends [(a: infer R) => any, ...AnyFunc[]]
? // Infer the argument type of the first function and pass it to `FlowFrom$`
FlowFrom$<R, T>
: // T was empty array
[];
// Actual function that deduces how much of a tuple is flowable, taking previous return type and remaining functions
type FlowFrom$<R, T extends readonly [(a: R) => any, ...AnyFunc[]]> = T extends [
(a: R) => infer R$,
...infer Tail
]
? Tail extends [(a: R$) => any, ...AnyFunc[]]
? // Valid, continue
[(a: R) => R$, ...FlowFrom$<R$, Tail>]
: // Tail has either been exhausted or invalid function has been found
Tail extends [(a: any) => any, ...AnyFunc[]]
? // Invalid function found, append a correct function type so it points out the specific error
// But make the appended function optional, the flow is still valid before this point
[(a: R) => R$, (a: R$) => any] | [(a: R) => R$]
: // Tail exhausted
[(a: R) => R$]
: // T is empty, exhausted tuple (impossible - apparently)
[];
FlowFrom
从给定的函数元组中推导出“可流动”的元组类型。本质上,它提取元组中所有将流入彼此的函数,当它遇到不再可流动的函数或元组耗尽时停止。
// All the functions in the tuple type are flowable - the return type is the same
let valid: FlowFrom<[(x: string) => number, (x: number) => number]>;
// ^ [(a: string) => number, (a: number) => number]
// First function won't flow into the second one - stop at first one
let invalid: FlowFrom<[(x: string) => number, (x: string) => number]>;
// ^ [(a: string) => number, (a: number) => any] | [(a: string) => number]
这意味着,当您尝试将 [(x: string) => parseInt(x, 10), (x: string) => x + '1']
分配给 invalid
时,您会得到一个很好的错误-
let invalid: FlowFrom<[(x: string) => number, (x: string) => number]> = [
(x: string) => parseInt(x, 10),
(x: string) => x + '1',
];
// ^ Type 'number' is not assignable to type 'string'
但是,你仍然可以在那个断点之前分配东西-
let reducedButCorrect: FlowFrom<[(x: string) => number, (x: string) => number]> = [
(x: string) => parseInt(x, 10),
];
(如果您不想要这种行为,例如 - 当存在断点时仍然能够在断点之前分配所有内容,请从 FlowFrom$
, [(a: R) => R$, (a: R$) => any] | [(a: R) => R$]
中删除联合类型-> [(a: R) => R$, (a: R$) => any]
)
您现在可以像这样使用它-
declare function flow<T extends readonly AnyFunc[]>(...args: T & FlowFrom<T>): unknown;
// Valid
flow(
(x: string) => parseInt(x, 10),
(x: number) => Boolean(x),
(x: boolean) => Number(x)
);
// Invalid
flow(
(x: string) => parseInt(x, 10),
(x: number) => Boolean(x),
(x: string) => Number(x)
);
// ^ Type 'boolean' is not assignable to type 'string'
上尝试所有这些
只想分享我的两分钱。