I'm new to generics in typescript, it's confusing
is there a way to make the spread operator ...
work in the snippet below
the issue
the line [ key: U, ...rest: Reg[U] ]
, doesn't work as I expect
the question
what am I doing wrong ?
I tried to make this work but couldn't
possible solutions i didnt try yet
function overloading
type Registery = {
"item.1": [name: string]
"item.2": [id: number, ready: "Y" | "N"]
"item.3": [ok: boolean]
}
type ReK = keyof Registery
const send = <
T extends ReK | ReK[] | "*" | never = never
>(
key: T,
callback: (
...args:
T extends Array<infer U>
? (
U extends ReK
? [ key: U, ...rest: Registery[U] ]
: never
)
: T extends string
? (
T extends ReK
? Registery[T]
: T extends "*"
? Registery[ReK]
: never
)
: never
) => any
) => { /** */ }
send("item.2", (...args) => {
const [
arg1,
//^?const arg1: number
arg2,
//^?const arg2: "Y" | "N"
] = args
})
send(["item.1", "item.2"], (key, ...args) => {
// ^?
const k = key
// ^?const k: "item.1" | "item.2"
if (key == "item.1") {
const [
arg1,
//^?const arg1: string | number
arg2,
//^?const arg1: string | number
] = args
}
if (key == "item.2") {
const [
arg1,
//^?const arg1: string | number
arg2,
//^?const arg2: string | number
] = args
}
})
here's a link to ts playground https://tsplay.dev/mxEQ7W
Your types are fine. The problem is that TypeScript doesn't support destructured discriminated unions when destructuring with a rest property. There is an open feature request at microsoft/TypeScript#46680 to support this, but until and unless that's implemented you have to work around it.
What you've done is equivalent to
type ParamUnion =
[key: "item.1", name: string] |
[key: "item.2", id: number, ready: "Y" | "N"];
const f: (...args: ParamUnion) => any =
(key, ...rest) => {
if (key === "item.1") {
rest[0].toUpperCase(); // error!
} else {
rest[0].toFixed(); // error!
}
};
where ParamUnion
is a discriminated union of tuple types where the first element is the discriminant.
That's the exact problem you have, with no mention of generics or T | T[] | "*"
or [ key: U, ...rest: Reg[U] ]
. That stuff is interesting but is essentially a red herring.
One way to work around this is not to use a rest element when doing your destructuring assignment of the ParamUnion
type into parameters:
const g: (...args: ParamUnion) => any =
(key, nameOrId, ready?) => {
if (key === "item.1") {
nameOrId.toUpperCase(); // okay
} else {
nameOrId.toFixed(); // okay
}
};
Another way to work around this is not to destructure the rest element at all, and instead just use a rest parameter:
const h: (...args: ParamUnion) => any =
(...args) => {
if (args[0] === "item.1") {
args[1].toUpperCase(); // okay
} else {
args[1].toFixed(); // okay
}
};
Both of these behave as desired.