i have put this code in the TS playground too, click here , hope it helps.
Like i have done in the animals
object using key in
to get the key as the enum ANIMALS values
i cannot find a way to do the same but like union types for the function param animalKey
. to get the something like 'cat' | 'lion' | 'parrot' | 'shark' | 'snail'
'cat' | 'lion' | 'parrot' | 'shark' | 'snail'
'cat' | 'lion' | 'parrot' | 'shark' | 'snail'
.
Any help would be much appreciated
enum ANIMALS {
CAT = 'cat',
LION = 'lion',
PARROT = 'parrot',
SHARK = 'shark',
SNAIL = 'snail'
}
interface iBaseAnimal {
name: string,
gender: 'male' | 'female'
wild: boolean
}
interface iShark extends iBaseAnimal {
max_gills: number
}
interface iParrot extends iBaseAnimal {
wing: {
length: 120,
unit: 'cm'
}
}
// DONE Overwritting property when extending base props with Omit
interface iSnail extends Omit<iBaseAnimal, 'gender'> {
gender: 'hermaphrodite'
}
interface iAnimals {
animals: {
// DONE Enum values as key
// PENDING way to interpolate proper interface value base on the enum key
[key in ANIMALS]: iBaseAnimal
},
// PENDING way to get Enum values as union types (similar to [key in ANIMALS] but for function param)
getAnimal: (animalKey:'lion', options: any) => void
}
Here is a possible solution:
enum AnimalsEn {
CAT = "cat",
LION = "lion",
PARROT = "parrot",
SHARK = "shark",
SNAIL = "snail"
}
interface iBaseAnimal {
name: string,
gender: "male" | "female"
wild: boolean
}
type AnimalsExtends = {
[AnimalsEn.SHARK]: {
max_gills: number
},
[AnimalsEn.PARROT]: {
wing: {
length: 120,
unit: "cm"
}
},
[AnimalsEn.SNAIL]: {
gender: "hermaphrodite"
},
}
// Utility to merge two Definition B object properties shadowing A object properties
type $Merge<TA, TB> = Omit<TA, keyof TB> & TB;
// Getting the Base+Extended type of an Animal
type iAnimal<A extends AnimalsEn> =
A extends keyof AnimalsExtends ?
$Merge<iBaseAnimal, AnimalsExtends[A]> :
iBaseAnimal;
interface iAnimals {
animals: {
// PENDING way to interpolate proper interface value base on the enum key
[key in AnimalsEn]: iAnimal<key> // just using utility to get the right Type
},
// Enum as a type ARE union of their posibles values
getAnimal: (animalKey: AnimalsEn, options: any) => void
}
You can improve this pattern as follow espacialy usefull if you plan to add/remove animal types later:
// a type for AnyAnimal
type AnyAnimal = { [key in AnimalsEn]: iAnimal<key> }[AnimalsEn];
// adding a type property on the finals interfaces
// this way you could easily discriminate animals by their type, refinement works very well with this
type iAnimal<A extends AnimalsEn> =
(A extends keyof AnimalsExtends ?
$Merge<iBaseAnimal, AnimalsExtends[A]> :
iBaseAnimal) & { type: A };
// you could use it very smoothly now :
export class NotExhaustiveError extends Error {
constructor (value: never) {
super(`Not allowed Value[${value}]`);
}
}
function doingSomethingWith(animal: AnyAnimal) {
// here animal is like
// {
// type: AnimalsEn,
// gender: "male" | "female" | "hermaphrodite";
// name: string,
// wild: boolean,
// }
const type = animal.type;
switch (type) {
case AnimalsEn.CAT: // here is a cat!
break;
case AnimalsEn.LION: // here is a lion!
break;
case AnimalsEn.PARROT: // here is a parrot!
break;
case AnimalsEn.SHARK: // here is a shark!
break;
case AnimalsEn.SNAIL: // here is a snail!
break;
default: // this will *throw* at *compile* time if you forgot to handle a case before
throw new NotExhaustiveError(type);
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.