Issue
How can I choose type according to generic property?
enum AnimalType {
DOG,
CAT,
FROG,
SNAKE,
}
const dog = {
type: AnimalType.DOG, // type property exists for all animals. other properties are vary for different animals
legs: 4,
}
const cat = {
type: AnimalType.CAT,
tail: 1,
}
const frog = {
type: AnimalType.FROG,
canSwim: true,
tail: 0,
}
const snake = {
type: AnimalType.SNAKE,
tail: 'looong',
age: 2,
}
type Animal<AnimalType> = typeof dog | typeof cat | typeof frog | typeof snake // how to rewrite this type definition?
How to infer type according to AnimalType? for example Animal<AnimalType.DOG>
should be equal to typeof dog
It's possible with conditional types:
type Animal<T extends AnimalType> = T extends AnimalType.DOG ? typeof dog : T extends AnimalType.CAT ? typeof cat : T extends AnimalType.FROG ? typeof frog : T extends AnimalType.SNAKE ? typeof snake : unknown
But it looks too long and totally unreadable. I hope there is a better solution
Solution
You probably intended
const dog = {
type: AnimalType.DOG,
legs: 4,
}
to have the type {type: AnimalType.DOG, legs: number}
. But TypeScript infers the type of AnimalType.DOG
to be the wider enum type AnimalType
instead of the literal type AnimalType.DOG
.
That means someone could write dog.type = AnimalType.CAT
, which is not good. And it also means that typeof dog
has no information you can use from which to compute Animal<T>
.
To fix this, you need to tell the compiler that type
should be given a more specific literal type. The usual approach for that is to use a const
assertion on the literal in question. You could do that for the full object literal {type: AnimalType.DOG, legs: 4} as const
, if you want the compiler to enforce that dog.legs
is always exactly 4
. But at the bare minimum we should do it for the type
property. Yielding this:
const dog = { type: AnimalType.DOG as const, legs: 4 };
const cat = { type: AnimalType.CAT as const, tail: 1 };
const frog = { type: AnimalType.FROG as const, canSwim: true, tail: 0 };
const snake = { type: AnimalType.SNAKE as const, tail: 'looong', age: 2 };
Now the union type typeof dog | typeof cat | typeof frog | typeof snake
looks like:
type SomeAnimal = typeof dog | typeof cat | typeof frog | typeof snake;
/* type SomeAnimal = {
type: AnimalType.DOG;
legs: number;
} | {
type: AnimalType.CAT;
tail: number;
} | {
type: AnimalType.FROG;
canSwim: boolean;
tail: number;
} | {
type: AnimalType.SNAKE;
tail: string;
age: number;
} */
which lets us implement Animal
via the union-filtering Extract
utility type:
type Animal<T extends AnimalType> =
Extract<SomeAnimal, { type: T }>;
Let's test it out:
type Dog = Animal<AnimalType.DOG>;
/* type Dog = {
type: AnimalType.DOG;
legs: number;
} */
Looks good.
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.