Issue
Given
type Loadable<T> = () => T
type LoadableCombinerResult<T> = { result: T }
I would like define types for a function with input like this:
- variable number of
Loadable<ResponseDataType>
inputs with differentResponseDataType
for each input, combiner
taking data results of above loadables.
The function would take care of handling error states and loading progress of loadables, plus few other things. The function would call combiner
only if all loadables are successfully loaded.
This is possible to implement in untyped JavaScript. However, I fail to properly type it in TypeScript.
Non-variadic typing would look like this:
function useLoadableCombiner2<TResult, T1, T2>(
combiner: (data1: T1, data2: T2) => TResult,
loadable1: Loadable<T1>,
loadable2: Loadable<T2>
): LoadableCombinerResult<TResult> { ... }
function useLoadableCombiner3<TResult, T1, T2, T3>(
combiner: (data1: T1, data2: T2, data3: T3) => TResult,
loadable1: Loadable<T1>,
loadable2: Loadable<T2>,
loadable3: Loadable<T3>
): LoadableCombinerResult<TResult> { ... }
function useLoadableCombiner4<TResult, T1, T2, T3, T4>(
combiner: (data1: T1, data2: T2, data3: T3, data4: T4) => TResult,
loadable1: Loadable<T1>,
loadable2: Loadable<T2>,
loadable3: Loadable<T3>,
loadable4: Loadable<T4>
): LoadableCombinerResult<TResult> { ... }
function useLoadableCombinerN<...>(...): LoadableCombinerResult<TResult> { ... }
Is it possible to type this in TypeScript as one function in one declaration?
It could be an array/typed-tuple instead of variable number of arguments.
The goal is to put in variable number of loadables in, and then being able to call typed combiner
with all the data, after a successful load of everything.
Solution
You can use a generic tuple type T
to represent the rest parameter list to combiner
, and then map over that tuple type to get the type of the loadable
rest parameter:
declare function useLoadableCombiner<R, T extends any[]>(
combiner: (...data: T) => R,
...loadable: { [I in keyof T]: Loadable<T[I]> }
): LoadableCombinerResult<R>;
Now when you call useLoadableCombiner
, the type checker can infer T
either from the parameter types of combiner
or, if you don't annotate those, from the return types of each element of loadable
:
const x = useLoadableCombiner(
// ^? const x: LoadableCombinerResult<boolean>
(str, num, dat) => str.length + num > dat.getTime(),
() => "a", () => 1, () => new Date()
)
Here T
is inferred from loadable
is of type [Loadable<string>, Loadable<number>, Loadable<Date>]
, and then combiner
is checked (you can see how even though we don't annotate str
, num
, and dat
, the type checker knows their types from loadable
) and returns a boolean
, and thus x
is of type LoadableCombinerResult<boolean>
.
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.