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
}
