假设我有一个函数数组,其中每个函数都接受前一个函数的返回值,并且我在该函数上调用
Array#reduce
,并使用数组中第一个函数接受的初始值。这是完全合理的,我希望返回类型是最后一个函数的返回类型。
但是 TypeScript 不允许我这样做(参见 Playground)。
更务实的是,我正在尝试编写一个通用的
pipe
函数,它将组合作为 ...rest
给出的函数,并将第一个参数“管道”到组合函数中:
function pipe(source, ...fns) {
return fns.reduce((value, fn) => fn(value), source);
}
我根本找不到一种方法来输入它,即使使用 varadic 元组类型。
即使我尝试递归地写出该函数,我也不太确定如何输入它:
function pipe<
S,
R,
Fns extends readonly unknown[],
>(source: S, ...fns: [(source: S) => R, ...Fns]): R {
if (fns.length === 0) {
return source;
}
const [fn, ...rest] = fns;
return pipe(fn(source), rest);
}
看游乐场。
它对你有用吗?
type Foo = typeof foo
type Bar = typeof bar
type Baz = typeof baz
type Fn = (a: any) => any
type Head<T extends any[]> =
T extends [infer H, ...infer _]
? H
: never;
type Last<T extends any[]> =
T extends [infer _]
? never : T extends [...infer _, infer Tl]
? Tl
: never;
// credits goes to https://stackoverflow.com/questions/55541275/typescript-check-for-the-any-type
type IfAny<T, Y, N> = 0 extends (1 & T) ? Y : N;
type IsAny<T> = IfAny<T, true, never>;
type HandleAny<T extends Fn, U> =
IsAny<Head<Parameters<T>>> extends true ?
(a: U) => ReturnType<T>
: T
type Allowed<
T extends Fn[],
Cache extends Fn[] = []
> =
T extends []
? Cache
: T extends [infer Lst]
? Lst extends Fn
? Allowed<[], [...Cache, Lst]> : never
: T extends [infer Fst, ...infer Lst]
? Fst extends Fn
? Lst extends Fn[]
? Head<Lst> extends Fn
? Head<Parameters<Fst>> extends ReturnType<Head<Lst>>
? Allowed<Lst, [...Cache, HandleAny<Fst, ReturnType<Head<Lst>>>]>
: never
: never
: never
: never
: never;
type LastParameterOf<T extends Fn[]> =
Last<T> extends Fn
? Head<Parameters<Last<T>>>
: never
type Return<T extends Fn[]> =
Head<T> extends Fn
? ReturnType<Head<T>>
: never
function compose<T extends Fn, Fns extends T[], Allow extends {
0: [never],
1: [LastParameterOf<Fns>]
}[Allowed<Fns> extends never ? 0 : 1]>
(...args: [...Fns] & Allowed<Fns>): (...data: Allow) => Return<Fns>
function compose<
T extends Fn,
Fns extends T[], Allow extends unknown[]
>(...args: [...Fns]) {
return (...data: Allow) =>
args.reduceRight((acc, elem) => elem(acc), data)
}
const foo = (arg: 1 | 2) => [1, 2, 3]
const bar = (arg: string) => arg.length > 10 ? 1 : 2
const baz = (arg: number[]) => 'hello'
/**
* Ok, but you need explicitly add allowed type
*/
const check = compose((a: string) => a, baz)([1, 2, 3]) // [number]
/**
* Errors
*/
// error because no type
const check_ = compose((a) => a, baz)([1, 2, 3])
// error because `a` expected to be string instead of number
const check__ = compose((a: number) => a, baz)([1, 2, 3])
在这里,在我的博客中,您可以找到解释。如果您仍然对这个问题感兴趣,请告诉我,我会尽力提供更多示例或示例。
发布答案,因为链接不适合评论:
type Last<T, Else = never> = T extends [...infer _, infer B] ? B : Else;
type Pipeline<
A,
Fns extends Array<(a: any) => any>,
Acc extends Array<(a: any) => unknown> = [],
> =
Fns extends [(a: A) => infer B, ...infer Tail]
? Tail extends Array<(a: any) => any>
? Pipeline<B, Tail, [...Acc, (a: A) => B]>
: never
: Acc
export default function pipe<
S,
Fns extends Array<(a: any) => any>,
>(
source: S,
...fns: Pipeline<S, Fns> extends Fns ? Fns : Pipeline<S, Fns>
): Fns extends [] ? S : ReturnType<Last<Fns>> {
// @ts-ignore
return fns.reduce((value, fn) => fn(value), source);
}