Search code examples
typescriptgenericstypesfactory-pattern

Which Typescript feature do I need to implement to have proper Type safety in this Factory Pattern?


I have now been going through the type script documentation back and forth for two days now, trying to find a workable approach to this problem I am trying to solve.

I found a really neat online approach on how to achieve type safety on passing in parameters to the function called "getAction", and vscode very nicely lets me choose only between "ActionA" and "ActionB", however the problem I am trying to solve is to pick up the exact type of that corresponding function. These various actions will not have identical payloads when the time comes to send over the payload, I need type safety on that.

The furthest I've come to solving this problem has been to pass over the return type as a generic, but this is prone to error, as I could be passing a wrong return type to the action I've selected.

The only proper way is to extract it somehow from the returned function. I've tried in numerous different ways and I've come to proposal to use Extract<Type,Union> and in the below code I've hard coded "string | number", but this needs to be replaced by some sort of code that extracts the type from the parameter passed to the selected property of actionTypes, which is always a function. (actionTypes["ActionA"] //returns (param: number)=>void). I need that number type of the param extracted and passed in as expected input value.

type Keys = keyof typeof actionMap
type actionTypes = typeof actionMap[Keys]

const actionA = (param: number) => {
  //does some action A
  console.log(param)
}

const actionB = (param: number) => {
  //does some action B
  console.log(param)
}

const actionMap = {
  ActionA: actionA
  ActionB: actionB
}

export class ActionFactory {
  static getAction(key: Keys): Extract<(param: string | number) => void, actionTypes> {
    return actionMap[key]
  }
}

const newAction = ActionFactory.getAction('ActionA')
newAction('Some Payload')

I'm new to type script and only started recently, so maybe I am totally missing the point, but any potential solution is worth a lot to me!

Thank you very much.


Solution

  • There's a very simple solution to your problem. If you use a generic type to evaluate the key which you provide to the function and use this to obtain the return value, typescript will be fully able to provide type-safety.

    const actionMap = {
      ActionA: (param: number) => { },
      ActionB: (param: string) => { }
    };
    
    export class ActionFactory {
      static getAction<K extends keyof typeof actionMap>(key: K): typeof actionMap[K] {
        return actionMap[key];
      }
    }
    
    const newAction = ActionFactory.getAction('ActionA');
    newAction('Some Payload'); // Error: Argument of type 'string' is not assignable to parameter of type 'number'