Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript second parameter type based on first parameter type

I have an enum of possible values for the first parameter and I want the second parameter to be based on the first parameter. So, if NAME is given I want the second parameter to be as string. If AGE is given, I want the second parameter to be a number.

How can I do something like this?

enum KeyType {
   NAME,
   AGE
}

class MyClass {
   public static setProperty(key: KeyType.NAME, value: string): void { }
   public static setProperty(key: KeyType.AGE, value: number): void { } 
}

And I would want to call the method like this:
MyClass.setProperty(KeyType.NAME, 'John');

Also, this should show an error:
MyClass.setProperty(KeyType.NAME, 5); // 5 is not a string

In this example it doesn't work as the key type is wrongly defined (key type is actually the value of the enum, so key type is 0).

I am also open to suggestions about a different approach to having this functionality of only allowing a specific type for a specific parameter key.

like image 570
XCS Avatar asked Nov 03 '17 09:11

XCS


People also ask

Can you pass a type as a parameter TypeScript?

To type a function as a parameter, type the function's parameter list and its return value, e.g. doMath: (a: number, b: number) => number . If the function's definition becomes too busy, extract the function type into a type alias.

How to give return Type in TypeScript?

To define the return type for the function, we have to use the ':' symbol just after the parameter of the function and before the body of the function in TypeScript. The function body's return value should match with the function return type; otherwise, we will have a compile-time error in our code.

How do you define a type of function parameter in TypeScript?

In TypeScript, generics are used when we want to describe a correspondence between two values. We do this by declaring a type parameter in the function signature: function firstElement < Type >( arr : Type []): Type | undefined {

Why avoid any in TypeScript?

❌ Don't use any as a type unless you are in the process of migrating a JavaScript project to TypeScript. The compiler effectively treats any as “please turn off type checking for this thing”. It is similar to putting an @ts-ignore comment around every usage of the variable.


2 Answers

You want to use function overloads to get the type checking working properly:

enum KeyType {
   NAME,
   AGE
}

class MyClass {
   public static setProperty(key: KeyType.NAME, value: string): void;
   public static setProperty(key: KeyType.AGE, value: number): void;
   public static setProperty(key: KeyType, value: (string | number)): void {}
}

or simpler, just use strings:

class MyClass {
   public static setProperty(key: 'name', value: string): void;
   public static setProperty(key: 'age', value: number): void;
   public static setProperty(key: string, value: (string | number)): void {}
}

MyClass.setProperty('name', 42); // Argument of type '"name"' is not assignable to parameter of type '"age"'.
MyClass.setProperty('age', 42); // Ok
MyClass.setProperty('name', 'foo'); // Ok
MyClass.setProperty('age', 'foo'); // Argument of type '"foo"' is not assignable to parameter of type 'number'.

And of course you don't have to list the literal strings in the overloads, you can group similar ones into a type definition:

type StringProperty = 'name' | 'address';
type NumberProperty = 'age' | 'salary';
class MyClass {
   public static setProperty(key: StringProperty, value: string): void;
   public static setProperty(key: NumberProperty, value: number): void;
   public static setProperty(key: string, value: (string | number)): void {}
}
like image 129
Duncan Avatar answered Oct 12 '22 06:10

Duncan


I'm using another approach, using an object.

For example: let's say that we are creating a function to send analytics data, so we have an event name and a payload about this event...

enum TEvents {
  productView = 'web/product/view',
  productPutInCart = 'web/product/put-in-cart'
}

type TLogEvent = ({ event, data }:
  {
    event: TEvents.productView,
    payload: {
      productId: number
    }
  } |
  {
    event: TEvents.productPutInCart,
    payload: {
      productId: number,
      amount: number
    }
  }
) => void

const logEvent: TLogEvent = ({ event, data }) => {
  ...
like image 31
macabeus Avatar answered Oct 12 '22 05:10

macabeus