Псевдонимы циклического типа не поддерживаются, за исключением некоторых случаев. Вместо того, чтобы пытаться представить конкретный тип, который вы там написали, дружественным к TypeScript, я думаю, что я сделаю резервную копию и интерпретирую ваш вопрос следующим образом: как мы можем напечатать flow()
-подобную функцию, которая принимает в качестве аргументов переменное число функций с одним аргументом, где каждый возвращаемый тип функции с одним аргументом является типом аргумента для следующей функции с одним аргументом, как цепочка ... и который возвращает функцию с одним аргументом, представляющую свернутую цепочку?
У меня есть кое-что, что, на мой взгляд, работает, но это довольно сложно, с использованием множества условных типов , спредов кортежей и сопоставленных кортежей . Вот оно:
type Lookup<T, K extends keyof any, Else=never> = K extends keyof T ? T[K] : Else
type Tail<T extends any[]> =
((...t: T) => void) extends ((x: any, ...u: infer U) => void) ? U : never;
type Func1 = (arg: any) => any;
type ArgType<F, Else=never> = F extends (arg: infer A) => any ? A : Else;
type AsChain<F extends [Func1, ...Func1[]], G extends Func1[]= Tail<F>> =
{ [K in keyof F]: (arg: ArgType<F[K]>) => ArgType<Lookup<G, K, any>, any> };
type LastIndexOf<T extends any[]> =
((...x: T) => void) extends ((y: any, ...z: infer U) => void)
? U['length'] : never
declare function flow<F extends [(arg: any) => any, ...Array<(arg: any) => any>]>(
...f: F & AsChain<F>
): (arg: ArgType<F[0]>) => ReturnType<F[LastIndexOf<F>]>;
Посмотрим, работает ли он:
const stringToString = flow(
(x: string) => x.length,
(y: number) => y + "!"
); // okay
const str = stringToString("hey"); // it's a string
const tooFewParams = flow(); // error
const badChain = flow(
(x: number)=>"string",
(y: string)=>false,
(z: number)=>"oops"
); // error, boolean not assignable to number
Хорошо выглядит для меня.
Я не уверен, стоит ли подробно рассказывать о том, как работают определения типов, но я мог бы также объяснить, как их использовать:
Lookup<T, K, Else>
пытается вернуть T[K]
, если может, в противном случае возвращается Else
. Так что Lookup<{a: string}, "a", number>
равно string
, а Lookup<{a: string}, "b", number>
равно number
.
Tail<T>
принимает тип кортежа T
и возвращает кортеж с удаленным первым элементом. Так что Tail<["a","b","c"]>
- это ["b","c"]
.
Func1
- это просто тип функции с одним аргументом.
ArgType<F, Else>
возвращает тип аргумента F
, если это функция с одним аргументом, и Else
в противном случае. Так что ArgType<(x: string)=>number, boolean>
равно string
, а ArgType<123, boolean>
равно boolean
.
AsChain<F>
принимает набор функций с одним аргументом и пытается превратить его в цепочку, заменяя тип возвращаемого значения каждой функции в F
на тип аргумента следующей функции (и используя any
за последний). Если AsChain<F>
совместим с F
, все хорошо. Если AsChain<F>
несовместимо с F
, то F
не является хорошей цепочкой. Итак, AsChain<[(x: string)=>number, (y:number)=>boolean]>
- это [(x: string)=>number, (y: number)=>any]
, что хорошо. Но AsChain<[(x: string)=>number, (y: string)=>boolean]>
- это [(x: string)=>string, (y: string)=>any]
, что нехорошо.
Наконец, LastIndexOf<T>
берет кортеж и возвращает последний индекс, который нам нужен для представления типа возврата flow()
. LastIndexOf<["a","b","c"]>
is 2
.
Хорошо, надеюсь, это поможет; удачи!