I have the following code in Typescript (simplified)
interface TimeoutOption {
category: 'timeout'
time: number
}
interface UserOption {
category: Parameters<typeof addEventListener>[0]
}
type MyDelayOptions = Array<TimeoutOption | UserOption>
function delay(options: MyDelayOptions) {
for (const option of options) {
if (option.category === 'timeout') {
timeout = setTimeout(() => {
// not relevant
}, option.time) // need to cast and do (option as TimeoutOption) to not get an error
}
}
}
In order to avoid a compilation error I have to add the type assertion mentioned in the comment. For a human though it is clear that if the category
is 'timeout'
my option
is of type TimeoutOption
. How can I make this work without the type assertion? Complete refactors welcome.
CodePudding user response:
The problem is that UserOption
defines category
as type string
(indirectly, but that's what it comes out as). As a result, if (option.category === 'timeout')
doesn't discriminate between your two union members, because UserOption
could easily have category: "timeout"
just like TimeoutOption
does. The discriminant (category
) has to clearly discriminate the union, but it doesn't in your case.
You could test for the presence of time
instead (see ***
below):
function delay(options: MyDelayOptions) {
for (const option of options) {
if ("time" in option) { // ***
const timeout = setTimeout(() => {
// not relevant
}, option.time) // need to cast and do (option as TimeoutOption) to not get an error
}
}
}
CodePudding user response:
Looks like you need a typeguard function here to confirm that option
is of type TimeoutOption
.
https://www.typescriptlang.org/docs/handbook/advanced-types.html
Something like this should do it.
function isTimeout(option: TimeoutOption | UserOption): option is TimeoutOption {
return (option as TimeoutOption).time !== undefined;
}
...
if (itTimeout(option) {
// do stuff
}