For example, I wrote the following code.
func({
items: [
{
event: 'a',
callback: (data) => {
},
},
{
event: 'b',
callback: (data) => {
},
},
{
event: 'c',
callback: (data) => {
},
},
],
});
I want is this.
Will it be possible?
The addEventListener
seems to be already working, so I tried referring to this part, but it didn't work out.
First you need to define a mapping between the string literal type of the event
property and the type of the intended callback
parameter data
. Luckily TypeScript has an easy way to represent mappings from string literal types to arbitrary types: it's just an object type. Like the following interface
:
interface EventMap {
a: A;
b: B;
c: C;
}
One we have that, we can define func()
as a generic function like this:
declare function func<T extends (keyof EventMap)[]>(arg: {
items: [...{ [I in keyof T]: {
event: T[I],
callback: (data: EventMap[T[I]]) => void
} }]
}): void;
Here the type parameter T
represents the tuple of event
properties in the items
array. So if you call func()
the way you show in your example, then we'd want T
to be ["a", "b", "c"]
.
When you call func()
, the compiler looks at the items
property of the argument and tries to match it with the type [...{[I in keyof T]: {event: T[I], callback: (data: EventMap[T[I]]) => void }]
.
That's a mapped tuple type which has been wrapped in the variadic tuple type [...
+]
to give the compiler a hint that you want T
to be inferred as a tuple and not an unordered array (see microsoft/TypeScript#39094 where it says "the type [...T]
, where T
is an array-like type parameter, can conveniently be used to indicate a preference for inference of tuple types").
And because {[I in keyof T]: {⋯T[I]⋯}}
is a homomorphic mapped type (see What does "homomorphic mapped type" mean?), the compiler will be able to infer T
from the elements of items
. For each element T[I]
at numeric index I
of the tuple type T
, the corresponding element of items
should have an event
property of that type T[I]
, and a callback
property whose data
callback parameter is of type EventMap[T[I]]
, which means we index into EventMap
with the key T[I]
. The intent is that the compiler will infer T[I]
from the event
property, and then use that to contextually type the callback
data
parameter.
Let's test it out:
func({
items: [
{
event: 'a',
callback: (data) => {
// ^?(parameter) data: A
},
},
{
event: 'b',
callback: (data) => {
// ^?(parameter) data: B
},
},
{
event: 'c',
callback: (data) => {
// ^?(parameter) data: C
},
},
],
});
// function func<["a", "b", "c"]>(⋯): void;
Looks good. The compiler inferred ["a", "b", "c"]
for T
as desired, and the data
parameter of each callback
is appropriately contextually typed.