Create a custom data structure that consolidates tuple variations to encompass all potential combinations of arguments accepted by your function.
For example:
enum MyEnum { A, B, C }
type HandlerArgs =
| [handler: MyEnum.A, payload: { a: string }]
| [handler: MyEnum.B, payload: { b: number }]
| [handler: MyEnum.C, payload: { c: boolean }]
The HandlerArgs
type connects specific payloads with corresponding enum values.
You can now construct an implementation similar to this:
class Foo {
async handle([handler, payload]: HandlerArgs) {
switch(handler) {
case MyEnum.A: {
console.log(payload.a) // works as expected
break
}
case MyEnum.B: {
console.log(payload.b) // functions appropriately
break
}
case MyEnum.C: {
console.log(payload.c) // operates correctly
break
}
}
}
}
This showcases how the type of payload
precisely conforms to the payload type based on the matched argument tuple.
See Playground
You could also abstract payload types from the parameters of functions where these payloads are utilized. This can be accomplished using the Parameters
type to extract the function arguments and infer the payload types accordingly.
For instance:
enum MyEnum { A, B, C }
type HandlerArgs =
| [handler: MyEnum.A, payload: Parameters<Foo['aHandler']['execute']>[0]]
| [handler: MyEnum.B, payload: Parameters<Foo['bHandler']['execute']>[0]]
| [handler: MyEnum.C, payload: Parameters<Foo['cHandler']['execute']>[0]]
... (remaining content remains unchanged)</answer1>
<exanswer1><div class="answer accepted" i="75676297" l="4.0" c="1678285645" m="1678296893" v="1" a="QWxleCBXYXluZQ==" ai="62076">
<p>Create a type that is a union of tuple types that represents the mapping of all the combinations of arguments your function accepts.</p>
<p>For example:</p>
<pre><code>enum MyEnum { A, B, C }
type HandlerArgs =
| [handler: MyEnum.A, payload: { a: string }]
| [handler: MyEnum.B, payload: { b: number }]
| [handler: MyEnum.C, payload: { c: boolean }]
The HandlerArgs
type binds specific payloads with specific enum values.
Now you can write an implementation like this:
class Foo {
async handle([handler, payload]: HandlerArgs) {
switch(handler) {
case MyEnum.A: {
console.log(payload.a) // fine
break
}
case MyEnum.B: {
console.log(payload.b) // fine
break
}
case MyEnum.C: {
console.log(payload.c) // fine
break
}
}
}
}
Here you can see that the type of payload
is getting correctly narrowed to the payload type in the arguments tuple that was matched.
See Playground
You could even pull the payload types from the arguments of the functions you plan to pass those payloads to. You would do this by using the Parameters
type to find the arguments of the functions and and derive that payload types from that.
For example:
enum MyEnum { A, B, C }
type HandlerArgs =
| [handler: MyEnum.A, payload: Parameters<Foo['aHandler']['execute']>[0]]
| [handler: MyEnum.B, payload: Parameters<Foo['bHandler']['execute']>[0]]
| [handler: MyEnum.C, payload: Parameters<Foo['cHandler']['execute']>[0]]
class Foo {
// mock some services this class uses
declare aHandler: { execute: (payload: { a: string }) => void }
declare bHandler: { execute: (payload: { b: number }) => void }
declare cHandler: { execute: (payload: { c: boolean }) => void }
async handle([handler, payload]: HandlerArgs) {
switch(handler) {
case MyEnum.A: {
this.aHandler.execute(payload)
break
}
case MyEnum.B: {
this.bHandler.execute(payload)
break
}
case MyEnum.C: {
this.cHandler.execute(payload)
break
}
}
}
}
Here
Parameters<Foo['aHandler']['execute']>[0]
drills into the type of
Foo.aHandler.execute
and finds the parameters of that function, and then uses the first parameter as the payload type.
See playground