const dispatch = {
'A': (arg: { a0: number }) => { },
'B': (arg: { b0: string, b1: number }) => { }
}
function Dispatcher<???>(key: ???, arg: ???) {
dispatch[key](arg);
}
function Dispatcher2<K extends keyof typeof dispatch>
(key: K, arg: Parameters<typeof dispatch[K]>[0]) {
dispatch[key](arg);// passing arg fails type checking
}
There are several ways to allow only the correct combinations of key
and arg
into Dispatcher
. However, I can never get TypeScript to understand that it's ok to pass the arg
into the function call.
Is there a way to do this?
CodePudding user response:
This problem can be put in the category of "correlated union" problems. When indexing dispatch
with key
, the compiler computes a union of functions which is only callable with an intersection of all parameters. The relationship between dispatch[key]
and arg
is essentially lost.
But there is a specific way to rewrite the types as described in PR#47109 to make the compiler understand the correlation.
We have to change the typing of dispatch
so that it uses a mapped type. The type DispatchRecord
acts as a lookup type for the parameter types.
type DispatchRecord = {
A: { a0: number }
B: { b0: string, b1: number }
}
const dispatch: {
[K in keyof DispatchRecord]: (arg: DispatchRecord[K]) => void
} = {
'A': (a) => { },
'B': (b) => { }
}
The function itself stays generic over K
. To type key
and arg
, we use a rest parameter. The type of the rest parameter is mapped type which computes a tuple consisting of two elements, one for key
and one for arg
. This mapped type is then indexed by K
.
function Dispatcher<K extends keyof DispatchRecord>(...[key, arg]: {
[P in K]: [P, DispatchRecord[P]]
}[K]) {
dispatch[key](arg)
}