Reputation: 152216
I've created a simple map with two functions that take a single 'foo: string' argument and implement a various functions that are directly returned:
import { mapValues } from 'lodash';
const fun1 = (foo: string) => (name: string, surname: string) => {};
const fun2 = (foo: string) => (age: number) => {};
interface Actions {
fun1: typeof fun1;
fun2: typeof fun2;
}
const actions: Actions = { fun1, fun2 };
const mappedActions = mapValues(actions, action => action('bar'));
mappedActions.fun1(1);
With mapValues
, I'm looping through those functions and invoke them with some common parameter. What I'd expect is a map of the semi-called functions assigned in mappedActions
map, but there is still a problem with the types.
fun1
is not resolved as a function that takes (name: string, surname: string)
arguments, but still the top level one.
How should I compose that to make TypeScript resolve the types correctly?
Upvotes: 3
Views: 340
Reputation: 26
I think I found a way to resolve types properly:
import { mapValues } from 'lodash';
const fun1 = (foo: string) => (name: string, surname: string) => {};
const fun2 = (foo: string) => (age: number) => {};
type FunctionDictionary = { [id: string]: ((...args: any[]) => any) };
interface Actions extends FunctionDictionary {
fun1: typeof fun1;
fun2: typeof fun2;
}
const actions: Actions = { fun1, fun2 };
type ReturnTypeDict<T> = T extends { [id: string]: ((...args: any[]) => any) } ? {
[P in keyof T]: ReturnType<T[P]>;
} : never;
const map: (actions: Actions) => ReturnTypeDict<Actions> = actions => {
return mapValues(actions, action => action('bar')) as ReturnTypeDict<Actions>;
}
const mappedActions = map(actions);
mappedActions.fun1(1);
The key here is the ReturnType conditional type. In order to use it you have to create your own conditional type:
type ReturnTypeDict<T> = T extends { [id: string]: ((...args: any[]) => any) } ? {
[P in keyof T]: ReturnType<T[P]>;
} : never;
And now you have to explicitly say that your interface extends needed dictionary type:
type FunctionDictionary = { [id: string]: ((...args: any[]) => any) };
interface Actions extends FunctionDictionary {
fun1: typeof fun1;
fun2: typeof fun2;
}
Then you create a function that properly maps data:
const map: (actions: Actions) => ReturnTypeDict<Actions> = actions => {
return mapValues(actions, action => action('bar')) as ReturnTypeDict<Actions>;
}
Upvotes: 1
Reputation: 2474
I copied your snippet to CodeSandbox and analyzed Lodash's function mapValues()
and I think types for mapValues()
not match your purpose.
mapValues<T extends object, TResult>(obj: T | null | undefined, callback: ObjectIterator<T, TResult>): { [P in keyof T]: TResult };
As you can see, function mapValues takes an object (T extends object
) and for each key of this object, assign TResult
which makes this:
mapValues<Actions, ((name: string, surname: string) => void) | ((age: number) => void)>(obj: Actions, callback: ObjectIterator<Actions, ((name: string, surname: string) => void) | ((age: number) => void)>): {
I tried to use lodash (especially lodash/fp) with TypeScript and I can say not everything works perfectly, also it's very hard to create universal typings for all possibilities.
Upvotes: 2