-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: String enums #3192
Comments
👍 We should definitely be able to use enums with string values. Even a
I'm not sure the automagical upper- and lowercasing described underneath Inferred enum special cases is such a good idea, though. I'd rather spell all cases exactly like they should be stringified: enum stringsUpperCase {
Shoes = "SHOES", // "SHOES"
BOOTS, // "BOOTS"
Feet // "Feet"
} |
Can this be generalized to other types (with the constraint that all types must be initialized - no value can be inferred)? |
I agree, and this could be the other special case (where value inference already exists for numbers). Also, FWIW, I made a post on that bug with a few more specific suggestions IIRC. It just wasn't on the top of my mind at the time. |
And I feel this syntax is a little repetitive in the enum strings : string {
Shoes, // "Shoes"
BOOTS, // "BOOTS"
Feet // "Feet"
} |
@jbondc Any advantages to that over my recent proposal? I don't think that regex-based solution is really that helpful in practice. All that regex does there is enforce type names, something better left to a linter as opposed to the main compiler. And it looks a little out of place compared to the enum entries. And as you said in the other thread, it will complicate the parser and compiler a little, likely more than it should. And as for extending enums, that's not very good practice, anyways. And it's generally not possible among other TypeScript users. Mine allows for such extensions, but you can only truly extend them in the same module or private namespace declaration, unless you're writing a definition file. That's because of the module wrapper emitted around every namespace declaration. So, for most purposes, you can't really extend them in the first place. Also, the spec currently requires that same-named declarations within the same root to add properties to the same enum. |
👍 for string (const) enums |
My current workaround looks something like this (this is a real-world, copy-and-paste code example): class Task {
..
static State = {
Unstarted: "Unstarted",
Started: "Started",
Completed: "Completed",
Faulted: "Faulted",
Canceled: "Canceled"
}
}
let task = new Task();
..
if (task.state === Task.State.Completed) // The declared type of task.state is "string" here.
console.log("Done!"); This also gives a nice encapsulation into the class type that I find appealing (through the Replicating this through I think having string enums would be very useful (maybe "necessary" should be the right word here). Automatic inferring of things like letters would be problematic because of locale and language issues (every language would have its own letters and ordering). Automatic lowercasing/uppercasing would also be impacted from similar issues (every language has different lower/uppercasing rules), and seems to me like a minor detail/enhancement at this point. |
Maybe the string enum PromiseState {
Pending,
Fulfilled,
Rejected
} In the above example, the string values of the cases would equal their names. They could be overwritten like this: string enum PromiseState {
Pending = "pending",
Fulfilled = "fulfilled",
Rejected = "rejected"
} |
This may turn out a bit long for a export const string enum PromiseState {
Pending,
Fulfilled,
Rejected
} So maybe an alternative using some sort of a psuedo-generic type parameter? export const enum PromiseState<string> {
Pending,
Fulfilled,
Rejected
} Due to issues with different languages, alphabets, and locales it would be difficult to apply a correct conversion to lowercase/uppercase in many languages other than English. So just preserving the original casing and allowing custom ones through |
Hi, what is the actual state of this proposal? can we have a roadmap to know when will be implemented in the language? Thanks |
@Gambero81 See #1206 for a more up-to-date version of this. |
Can this be closed in favor of #1206? It's more general, which is better IMHO. |
Union and intersection types would already exist with my proposal. You On Sun, Sep 20, 2015, 09:17 Jon notifications@github.com wrote:
|
Using of String are really neat feature. In our 20k loc project we still use workarounds. |
Probably better if #1003 is done instead |
@basarat I think both have their uses. A primitive enum (excluding symbols) could be seen as the union of their values in that sense. And also, I believe that should be expanded to also include numbers. |
I made a relevant response over there on #1003. |
@jbondc I tried to include those, but I didn't get a very positive response from it. |
@basarat Thanks, I've just discovered related #1003, #1295. Great proposals/ideas!
var var1 = MyEnum.Kind1; //string enum.
var var2 = "kind1"; //string literal union type - NO compiler type-checking!
var var3:MyUnionEnum = "kind1"; //string literal union type with compiler type-checking
Apart, from all above, Javascript ecosystem generally encourage wide usage of string literals. As result, like in #1003, #1295 IDEs/Tools will be forced to support special meaning of string literals, like as enum constants/properties names etc, not just meaningless text to output. |
👍 This feature would be great. Right now, the It would be especially useful for bridging the gap between both worlds: Example:
Right now, |
👍 |
My two cents while this gets approved: Not that you can pretend this feature like this. export enum OrderType {
Ascending = "Ascending" as any,
Descending = "Descending" as any,
} The benefit is that then your API is better documented, asking for an The problem is that you need to repeat the identifier for the common case when declaring. Also, when using it, you have to cast from var orderType: OrderType = "Ascending"; //Error
var orderType: OrderType = "Ascending" as any; //OK |
@weswigham Given your example, how come the final line here fails? function mkenum<T extends {[index: string]: U}, U extends string>(x: T) { return x; }
const Cardinal = mkenum({
North: 'N',
South: 'S',
East: 'E',
West: 'W'
});
type Cardinal = (typeof Cardinal)[keyof typeof Cardinal];
const p = Cardinal.East
const x = { y: Cardinal.East }
interface z { foo: Cardinal.East } // 'Cardinal' only refers to a type, but is being used as a value here. |
Here's my workaround for string enums with type and value position support, until 2.1 lands with // Boilerplate for string enums until 2.1 (keyof)
// String enums can be used in type and value positions.
// https://github.com/Microsoft/TypeScript/issues/3192#issuecomment-261720275
export class ActionTypes {
public static Foo: 'Foo' = 'Foo'
}
interface FooAction extends ReduxAction {
type: typeof ActionTypes.Foo
} |
@OliverJAsh The error in #3192 (comment) is correct. You are using an "enum value" ( If you want to define a type that has a key that can only ever have the value |
@weswigham just a note about the enum NumericEnum {
A = 1
}
let a = NumericEnum.A // a inferred type will be NumericEnum, good
let b = Cardinal.North // b inferred type will be string, bad, is too generic, so ...
let c : Cardinal = Cardinal.North // to have an useful type we must explicitely type c |
Here is a // original version:
function mkenum<T extends {[index: string]: U}, U extends string>(x: T) { return x; }
// new:
function mkenum2<X extends {[i:string] : any}, K extends string>(x : X )
: {[K in (keyof X) ] : K} {
let o : any = {}
for(let k in x)
o[k] = k;
return o
}
// to define the type in a more succint way, for both versions:
export type enumType<T> = T[keyof T]
// define enums using the original and the new version:
const Colors = mkenum({"Red" : "Red",
"Green" : "Green"});
type Colors = enumType<typeof Colors>
const Colors2 = mkenum2({"Red" : 1,
"Green" : 1}); // strings specified once, not twice
type Colors2 = enumType<typeof Colors2>
let a = Colors.Red // string
let a2 = Colors2.Red // "Red"
let b : Colors = Colors.Red // "Red" | "Green"
let b2 : Colors2 = Colors2.Red // "Red" | "Green" There is a little difference, the Questions:
|
Now with typescript 2.1 and keyof, there is a better implementation of string enum? and what about inline version for const string enum? |
Per your third question: function mkenum3<X extends string>(...x:X[]):{[K in X]:K } {
const o:any = {};
for (const k in x)
o[k] = k;
return o;
}
type enumType<T> = T[keyof T];
const Colors3 = mkenum3('Red', 'Green');
type Colors3 = enumType<typeof Colors3>; |
@igrayson Awesome solution! But I think your loop is incorrect. It should be
that is, iterate over the values of the array not the keys. When I ran your version, the types were correct, but the runtime value of |
I use the techniques here in several of my projects, so I turned them into an NPM module for easy access. See https://github.com/dphilipson/typescript-string-enums. I don't mean to claim credit for this solution, and I did my best to credit the users who came up with it in the readme. Hopefully others will find this useful. |
The runtypes library allows defining a runtime type for a union of literals and then extracting both the static type as well as its enumerated values. Example usage: // Define the runtype
const Day = Union(
Literal('Sunday'),
Literal('Monday'),
Literal('Tuesday'),
Literal('Wednesday'),
Literal('Thursday'),
Literal('Friday'),
Literal('Saturday'),
)
// Extract the static type
type Day = Static<typeof Day> // = 'Sunday' | 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday' | 'Saturday'
// Extract enumerated literal values
const days: Day[] = Day.alternatives.map(lit => lit.value)
for (const day of days) {
console.log(`Good morning, it's ${day}!`)
} |
With custom transformers introduced by #13940, you can create string enum from string literal types. import { enumerate } from 'ts-transformer-enumerate';
type Colors = 'green' | 'yellow' | 'red';
const Colors = enumerate<Colors>(); // type of Colors is { [K in Colors]: K }
console.log(Colors.green); // 'green'
console.log(Colors.yellow); // 'yellow'
console.log(Colors.red); // 'red' The above code is compiled to the following JavaScript. var ts_transformer_enumerate_1 = require("ts-transformer-enumerate");
var Colors = { green: "green", yellow: "yellow", red: "red" }; // type of Colors is { [K in Colors]: K }
console.log(Colors.green); // 'green'
console.log(Colors.yellow); // 'yellow'
console.log(Colors.red); // 'red' See https://github.com/kimamula/ts-transformer-enumerate. |
Implementation now available in #15486. |
Let's track this at #1206 |
A string enum is inferred by looking at the constant value of the initializer of its first member:
Or it is declared using a string index:
The auto initialized value on an enum is its property name.
Inferred enum special case
The text was updated successfully, but these errors were encountered: