Home > Mobile >  TypeScript: Idiomatic way to do a switch-case on Enum to set a variable
TypeScript: Idiomatic way to do a switch-case on Enum to set a variable

Time:09-21

Example problem: I have an Enum variable Difficulty. In a function, I want to set the config DifficultyConfig depending on the value of Difficulty. Here's an inelegant way that I can think of:

export interface DifficultyConfig {
    healthModifier: number,
    deathIsPermanent: boolean,
}

export interface AppProps {
    difficultyConfig: DifficultyConfig
}

export const NormalDifficultyConfig: DifficultyConfig = {
    enemyHealthModifier: 1,
    deathIsPermanent: false,
}

export const HigherDifficultyConfig: DifficultyConfig = {
    enemyHealthModifier: 1.3,
    deathIsPermanent: true, 
}

export enum Difficulty {
    NORMAL = 'Normal',
    HARD = 'Hard',
    ADVANCED = 'Advanced',
}

function createApp(difficulty: Difficulty) {
    let difficultyConfig: DifficultyConfig;
    
    switch(difficulty) {
        case Difficulty.NORMAL: 
            difficultyConfig = NormalDifficultyConfig;
            break;
        // Both HARD and ADVANCED get HigherDifficultyConfig
        case Difficulty.HARD:
            difficultyConfig = HigherDifficultyConfig;
            break;
        case Difficulty.ADVANCED: 
            difficultyConfig = HigherDifficultyConfig;
            break;
        default: 
            difficultyConfig = NormalDifficultyConfig;
            break;
    }
    
    return new App({
        difficultyConfig
    });
}

I'm not fond of the Switch case syntax for something so simple. My ideal would be something like this in Scala:

val difficultyConfig = difficulty match {
    case Difficulty.NORMAL => NormalDifficultyConfig
    case Difficulty.HARD | Difficulty.ADVANCED => HigherDifficultyConfig
    case _ => NormalDifficultyConfig
}

Is there an equivalent for this in JavaScript?

CodePudding user response:

If there's a difference in logic, a switch or an if/else if/else (slightly less verbose) is probably the way to go (more on this below though). If it's purely data as in your example, then you could use a difficulty-to-config mapping object:

const difficultyConfigs = {
    [Difficulty.NORMAL]: NormalDifficultyConfig,
    [Difficulty.HARD]: HigherDifficultyConfig,
    [Difficulty.ADVANCED]: HigherDifficultyConfig,
} as const;

You might even declare that as Record<Difficult, DifficultyConfig> like this, as caTS points out in the comments:

const difficultyConfigs: Record<Difficult, DifficultyConfig> = {
//                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    [Difficulty.NORMAL]: NormalDifficultyConfig,
    [Difficulty.HARD]: HigherDifficultyConfig,
    [Difficulty.ADVANCED]: HigherDifficultyConfig,
} as const;

More on that in a minute, but either way, then the function is just:

function createApp(difficulty: Difficulty) {
    let difficultyConfig = difficultyConfigs[difficulty];
    
    return new App({
        difficultyConfig
    });
}

Playground links: Without Record | With Record (I also updated a couple of seeming typos/editing errors in the question's code.)

This also has the advantage that if you add a Difficulty but forget to include one in difficultyConfigs, you get a handy compile-time error when you use it. I've simulated such an error here by adding a MEDIUM difficulty but "forgetting" to update the function.

But better yet, if we include the type Record<Difficulty, DifficultyConfig> type as caTS suggested and difficultyConfigs doesn't have an entry for every Difficulty value, you get an even earlier compile-time error, like this.


Even for logic, if you like you can use the same sort of concept to create a dispatch object:

const difficultyConfigs = {
    [Difficulty.NORMAL]: () => { /*...build and return NORMAL config...*/ },
    [Difficulty.HARD]: () => { /*...build and return HARD config...*/ },,
    [Difficulty.ADVANCED]: () => { /*...build and return ADVANCED config...*/ },,
} as const;
// ...
const difficultyConfig = difficultyConfigs[difficulty]();
  • Related