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;
}
};
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.
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.
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.
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.
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 enum
s 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With