Reputation: 9151
I'm looking into type narrowing in TS. They provide this example to check one type. It compiles to a function returning a bool.
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
What I would like though is a function that can take multiple types and spits out the type. Something like this:
const isAnimalType = (
animal: Cat | Dog | Bird | Fish
): animal is Cat | Dog | Bird | Fish =>
{
switch (true) {
case animal instanceof Cat:
return is Cat;
case animal instanceof Dog:
return is Dog;
...
}
How would one do such a thing in TS?
Upvotes: 2
Views: 966
Reputation: 327884
The only way this would work is if isAnimalType()
could return four different values. After all, you'd need to actually examine the return value and do something specific to Cat
or Dog
or Bird
or Fish
with it. So, maybe it returns one of the four strings "cat"
or "dog"
or "bird"
or "fish"
. Then you could maybe use it like this:
// this doesn't work, sorry
if (isAnimalType(animal) === "cat" /* or some value */) {
animal.meow();
}
where isAnimalType(animal) === "cat"
would automatically narrow animal
to type Cat
, so animal.meow()
would type-check. No matter what, you'd need four different values to come out of isAnimalType()
.
But user-defined type guard functions must return boolean
values, and there are only two of those: true
and false
. So it's just not possible in TypeScript as of now.
There is an open feature request at microsoft/TypeScript#46650 asking for type guard functions that return non-boolean
values, and if this were implemented (although there's no indication that this will happen) then maybe you'd have some way to implement this. But for now it is not possible.
Two is less than four.
But: there's not a lot of difference between if (isAnimalType(animal) === "cat")
and if (isAnimalType(animal, "cat"))
. The former doesn't work with a boolean
return type, but the latter does. And so, if you want, you could do something like this:
interface AnimalMap {
cat: Cat,
dog: Dog,
bird: Bird,
fish: Fish
}
const isAnimalType = <K extends keyof AnimalMap>(
animal: AnimalMap[keyof AnimalMap],
animalType: K
): animal is AnimalMap[K] => {
return animal instanceof (
{ cat: Cat, dog: Dog, bird: Bird, fish: Fish }
)[animalType];
}
Which now works as desired:
if (isAnimalType(animal, "cat")) {
animal.meow(); // okay
}
So that might be a workaround, depending on your use case.
Upvotes: 3