I have a the following mapper with the type associated with, but I have no clue what to look, to type func
correctly.
type Action<T, K> = {
key: K;
func: // What should this be?
};
type ActionMapper<T> = {
[K in keyof T]: Action<T, K>;
};
export enum EActionSelectPot {
CREATE_ORDER = 'CREATE_ORDER',
ADD_ITEM = 'ADD_ITEM',
DELETE_ORDER = 'DELETE_ORDER',
}
const first_function = () => 'hi';
const second_function = () => ['hi'];
const third_function = () => {
hi: 'hi';
};
const test: ActionMapper<typeof EActionSelectPot> = {
CREATE_ORDER: {
key: EActionSelectPot.CREATE_ORDER,
func: first_function,
},
ADD_ITEM: {
key: EActionSelectPot.ADD_ITEM,
func: second_function,
},
DELETE_ORDER: {
key: EActionSelectPot.DELETE_ORDER,
func: third_function,
},
};
const hi = test.CREATE_ORDER.func();
My goal is pretty straightforward, I have to have an object that infer
the type of the function with generics.
The problem is that I have no clue if this is possible at all, if so, is there any way to accomplish such thing.
I've tried with func: () => ReturnType[K]['func']>;
, but I really don't know where to look for.
I don't want to infer manually the type of the return, can it be fully dynamic ? Where to look for ?
The generic ActionMapper<T>
type you're looking for is
type ActionMapper<T> = {
[K in keyof T]: Action<K, T[K]>;
};
where Action
is
type Action<K, V> = {
key: K;
func: () => V;
};
But, unfortunately, you can't ask the compiler to infer T
for you in generic types. That means there's no way to write something like:
// don't do this, it won't work:
const test: ActionMapper<infer> = {
[EActionSelectPot.CREATE_ORDER]: {
key: EActionSelectPot.CREATE_ORDER,
func: first_function,
},
[EActionSelectPot.ADD_ITEM]: {
key: EActionSelectPot.ADD_ITEM,
func: second_function,
},
[EActionSelectPot.DELETE_ORDER]: {
key: EActionSelectPot.DELETE_ORDER,
func: third_function,
},
};
and have T
be inferred as if you had written
const test: ActionMapper<{
[EActionSelectPot.CREATE_ORDER]: string,
[EActionSelectPot.ADD_ITEM]: string[],
[EActionSelectPot.DELETE_ORDER]: { hi: string }
}> = { ⋯ };
There is a longstanding open feature request for this at microsoft/TypeScript#32794, but for now it's not part of the language. You have to work around it.
The standard workaround is to use generic functions to help. TypeScript will infer type arguments when you call generic functions, so you can make a function whose sole purpose is to give you that inference:
const actionMapper = <T,>(a: ActionMapper<T>) => a;
That's an identity function at runtime, it's just a => a
, and so const test = actionMapper(xxx)
and const test = xxx
are the same at runtime. But TypeScript can use actionMapper(xxx)
to guide the inference for xxx
and thus for test
:
const test = actionMapper({
[EActionSelectPot.CREATE_ORDER]: {
key: EActionSelectPot.CREATE_ORDER,
func: first_function,
},
[EActionSelectPot.ADD_ITEM]: {
key: EActionSelectPot.ADD_ITEM,
func: second_function,
},
[EActionSelectPot.DELETE_ORDER]: {
key: EActionSelectPot.DELETE_ORDER,
func: third_function,
},
})
test
//^? const test: ActionMapper<{
// [EActionSelectPot.CREATE_ORDER]: string,
// [EActionSelectPot.ADD_ITEM]: string[],
// [EActionSelectPot.DELETE_ORDER]: { hi: string }
// }>
That's the right type, and it was inferred for you, as desired. And while the actionMapper
function is a workaround, it's not really much different for the developer from what you wanted. Instead of const test: ActionMapper<infer> = { ⋯ }
they write const test = actionMapper(⋯)
.