in JavaScript I often (ab)use objects as pseudo-enums:
const application = {
ELECTRIC: {propA: true, propB: 11, propC: "eee"},
HYDRAULIC: {propA: false, propB: 59, propC: "hhh"},
PNEUMATIC: {propA: true, propB: 87, propC: "ppp"},
}
const foo = application.ELECTRIC
const bar = application.HYDRAULIC
I'd like to achieve a similar behavior in TypeScript, but in a type-safe manner. Here's what I have so far:
type Application = "ELECTRIC" | "HYDRAULIC" | "PNEUMATIC";
interface ApplicationProps {
propA: boolean;
propB: number;
propC: string;
}
const application: Record<Application, ApplicationProps> = {
ELECTRIC: {propA: true, propB: 11, propC: "eee"},
HYDRAULIC: {propA: false, propB: 59, propC: "hhh"},
PNEUMATIC: {propA: true, propB: 87, propC: "ppp"},
}
const foo = application.ELECTRIC;
---
let bar: ApplicationProps; // an attempt to restrict bar to application.ELECTRIC|application.HYDRAULIC|application.PNEUMATIC
bar = application.HYDRAULIC;
That works okay but feels clumsy.
1) There seems to be lot of repetition in type/interface/object
Is there some TS-Trickery to automatically infer type Application
/ interface ApplicationProps
from the object? Like some keyof typeof something
magic?
2) It feels strange to type bar
as ApplicationProps
when I actually want to restrict it to members of application
Is there a more elegant way to restrict bar
to application.ELECTRIC|application.HYDRAULIC|...
?
3) If I want to define bar
in another file, I need to import ApplicationProps
and application
. I know the type info is part of application
but I don't know how to get it.
The best I got so far was: let bar: typeof application[keyof typeof application]
Which of course is stupid. There has to be a better way?!
Thanks in advance :)
CodePudding user response:
So you can do something like this:
// all application keys will be defined here
// as const is needed here so typescript won't start widening the type to string[]
const applications = ["ELECTRIC", "HYDRAULIC", "PNEUMATIC"] as const
// get all keys as a Union Type
type ApplicationKeys = typeof applications[number]
// create your enum object as a type
type Application = { [Key in ApplicationKeys]: ApplicationProps<Key> }
// define your props with a key so typescript can differentiate between your enums
type ApplicationProps<T> = {
key: T,
propA: boolean;
propB: number;
propC: string;
}
// the actual enum definition
const application: Application = {
ELECTRIC: { key: "ELECTRIC", propA: true, propB: 11, propC: "eee" },
HYDRAULIC: { key: "HYDRAULIC", propA: false, propB: 59, propC: "hhh" },
PNEUMATIC: { key: "PNEUMATIC", propA: true, propB: 87, propC: "ppp" },
}
// test
let foo = application.ELECTRIC;
let bar: Application["PNEUMATIC"];
bar = application.HYDRAULIC; // error: Type 'ApplicationProps<"HYDRAULIC">' is not assignable to type 'ApplicationProps<"ELECTRIC">'.
bar ===foo // error his condition will always return 'false' since the types have no overlap
The neat part is that if you want to add a new application key you just have to add it to the list and typescript will throw errors for the missing or unhandled key in a switch or if statement(exhaustive switch block)[https://stackoverflow.com/questions/39419170/how-do-i-check-that-a-switch-block-is-exhaustive-in-typescript].
I hope you understand my bad english :) CODE
CodePudding user response:
I sometimes do something like this, with having a enum list mapping to specific types.
But I'm also curious about how this could be done better.
enum ApplicationType {
ELECTRIC = 'ELECTRIC',
HYDRAULIC = 'HYDRAULIC',
PNEUMATIC = 'PNEUMATIC',
}
interface Application {
propA: boolean;
propB: number;
propC: string;
}
interface ElectricApplication extends Application {
propD: string;
}
interface PneumaticApplication extends Application {}
interface HydaulicApplication extends Application {}
type ApplicationTypeMap = {
[ApplicationType.ELECTRIC]: ElectricApplication;
[ApplicationType.HYDRAULIC]: HydaulicApplication;
[ApplicationType.PNEUMATIC]: PneumaticApplication;
};
const applications: { [key in ApplicationType]: ApplicationTypeMap[key] } = {
ELECTRIC: {
propA: true,
propB: 11,
propC: 'eee',
propD: 'boo',
},
HYDRAULIC: { propA: false, propB: 59, propC: 'hhh' },
PNEUMATIC: { propA: true, propB: 87, propC: 'ppp' },
};
const foo = applications[ApplicationType.ELECTRIC];
console.log(foo.propD);
const bar = applications[ApplicationType.HYDRAULIC];
console.log(bar.propA);