Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use Typescript enum values to create new type

Tags:

typescript

I have the following scenario:

enum FieldsMap {
  User = "user-name",
  Password = "user-password",
  Country = "user-country"
}

type Fields = "user-name" | "user-password" | "user-country";

as you can see Fields is repeating the values of FieldsMap, is there a way that Fields can use the values of FieldsMap in order to avoid repetition?. Also, I'm using FieldsMap as an enum here, but I can change that if necessary, I'm just trying to avoid using strings as much as possible:

const onChangeHandler = (key: Fields, value: string) => {
  switch (key) {
    case FieldsMap.User:
      // do something with `value`
      break;
    case FieldsMap.Password:
      // do something with `value`
      break;
    case FieldsMap.Country:
      // do something with `value`
      break;
  }
};
like image 785
a.guerrero.g87 Avatar asked Jul 01 '19 19:07

a.guerrero.g87


People also ask

Can I use enum as a type in TypeScript?

Enums are one of the few features TypeScript has which is not a type-level extension of JavaScript. Enums allow a developer to define a set of named constants. Using enums can make it easier to document intent, or create a set of distinct cases. TypeScript provides both numeric and string-based enums.

Can we change enum values in TypeScript?

An enum is usually selected specifically because it is immutable - i.e. you would never want the values to change as it would make your application unpredictable.

Is enum a type or value TypeScript?

In TypeScript, enums, or enumerated types, are data structures of constant length that hold a set of constant values. Each of these constant values is known as a member of the enum. Enums are useful when setting properties or values that can only be a certain number of possible values.

Can enum be used as variable type?

An enum type is a special data type that enables for a variable to be a set of predefined constants. The variable must be equal to one of the values that have been predefined for it.


1 Answers

Update for TS4.1+: as the other answer points out, you can now use template literal types to get the string representation of the values of the FieldsMap enums:

type Fields = `${FieldsMap}`;
// type Fields = "user-name" | "user-password" | "user-country"

But it's important to be sure you actually need this; the ordinary use case for enums is that you don't want much of a dependency on the actual string literal values themselves; for an enum named Foo, the type Foo is already a union of the enum values, but they are treated nominally so you can't accidentally use the string in place of it. If you really want to use the string in place of the value, it's possible you don't want an enum in the first place and should just use objects with string literals in them. The point is: make sure this is what you really want before using it.


Just writing this out as an answer... in TypeScript, enums essentially already have the behavior you're looking for without needing to define any additional types.

That is, the following enum definition:

enum Fields {
  User = "user-name",
  Password = "user-password",
  Country = "user-country"
}

brings into scope a value named Fields, with properties whose keys are User, Password, and Country, and whose values are "user-name", "user-password", and "user-country", respectively, as you know. This value makes it to runtime, as in:

const x = Fields.User; // makes it to the emitted JS

and is similar to the value:

const FieldsLike = {
  User: "user-name",
  Password: "user-password",
  Country: "user-country"
} as const;

const xLike = FieldsLike.User; // makes it to the emitted JS;

But it also brings into scope a type named Fields, which is equivalent to the union of the property values in the Fields object. This type is erased with the rest of the type system when JS is emitted, but can be accessed at design time via type annotations and using IntelliSense, as in:

const y: Fields = x; // the "Fields" annotation is the type

and is similar to the type

type FieldsLike = (typeof FieldsLike)[keyof typeof FieldsLike];
// type FieldsLike = "user-name" | "user-password" | "user-country"

const yLike: FieldsLike = xLike; // the "FieldsLike" annotation is the type

This is the type you're looking for; see below.

By the way, the enum also acts as a namespace with exported types for each enum member:

const z: Fields.Password = Fields.Password; // type and value

(so the Fields.Password on the left is the name of a type, while the Fields.Password on the right is the name of a value). Thus the types accessible under Fields are similar to the namespace:

namespace FieldsLike {
  export type User = typeof FieldsLike.User;
  export type Password = typeof FieldsLike.Password;
  export type Country = typeof FieldsLike.Country;
}

const zLike: FieldsLike.Password = FieldsLike.Password; // type and value

Whew, that means just using the enum Fields { ... } definition give us the behavior of const FieldsLike = ..., type FieldsLike = ..., and namespace FieldsLike all at once.

Let's back up... the type you were looking for, the union of all properties under the Fields enum, is already a type named Fields. (Okay, I changed the name of your FieldsMap to Fields), and you can use it directly:

const onChangeHandler = (key: Fields, value: string) => {
  switch (key) {
    case Fields.User:
      // do something with `value`
      break;
    case Fields.Password:
      // do something with `value`
      break;
    case Fields.Country:
      // do something with `value`
      break;
  }
};

Okay, hope that helps. Good luck!

Link to code

like image 164
jcalz Avatar answered Sep 27 '22 20:09

jcalz