hmm, good question! I don't typically use classes much, but if that's the pattern you're using, I think classes are fine. The nice thing about classes is that they are a type and a value in BOTH TypeScript and JavaScript, so the behaviour is pretty consistent: that is, you can do `myObj instanceof MyClass` in both TS & JS. One thing to keep in mind is that JavaScript doesn't (yet) have private properties within classes, so any private properties in your TypeScript classes will be normally-accessible properties once transpiled to JavaScript. edit: put together some quick examples here: shaky.sh/ts-private-properties/
Thanks to share your thoughts, Andrew ! Actually, I have difficulty to find a good use case for real classes. I use Java for a long time and JS for 5 years. It seems unnatural in TS, in part, because TS only need the shape of the object to consider the type. Whatever, you have a point to mention the instanceOf operator available in both TS and JS worlds, pretty consistent indeed. Nice article to private properties trick.
I’ve used enums to great effect in my project. In regards to accept user input and start using it directly in relation to a string enum, I think that in itself is a vad practice and you should always sanitize and qualify the input anyway to see if it matches the enum. So I actually like TS insistance on making devs explicitly use the enum instead of passing in the string.
Great video! After watching I started thinking, and this is the result: export type Enum = T[keyof T]; const ABC = { A: 'a', B: 'b', C: 'c' } as const; let X: Enum = ABC.B;
The last example is what I ended up doing myself many times. Super frustrating to work with enums. Sometimes they work fine, other times you just end up regretting using them.
Also worth noting that in your example, you could have just named your type "PostState" and the const "PostState" instead of "PostStateType" which is more awkward. Because of declaration merging, this is allowed.
Still new to TypeScript, but at this point, why not just set the type for PostState as a union of strings ("DRAFT" | "SCHEDULED" | "PUBLISHED")? There's a lot of mental overhead for me to process what you used in the end. You still get auto completion. Anything else this doesn't get you? Or is it purely the look of raw strings vs. the more contextual syntax that repeats the PostState part in PostState.Draft e.g.?
You're absolutely right. You don't get anything more than the PostState.Draft object notation vs the "DRAFT" string literal. To me this is purely a code aesthetic choice. When self documenting code is important, especially with multiple developers writing library code with the same enum, then Andrews pattern is nice. But its often overkill.
Are there any good use cases for enums beyond when you essentially want named numbers? I used to use them liberally but then found just using unions of strings way more ergonomic.
@@igorswies5913 Oooh, gotcha! Yeah, that's a good example! The best you get in that case is that after you update the union, the type checker will tell you all the places that no longer match the union. But you'll have to update them manually :(
In which scenario would using the same name for the type as the variable be a problem? Surely the type names don't have any relevance on the variable names in typescript, isn't the language always lexicographically disambiguous in whether you're refering to a type or a variable?
I use a very similar solution, but instead of "as const", I wrap my object literal with Object.freeze, which has the same TypeScript benefit of "as const" and also protects your object at runtime from someone making changes to it.
I ran into an interesting problem with this pattern. If you want to use an enum-like for a generic argument, an actual enum will work, but the as const version will not. If you try to pass Post, it will complain that PostStateType is not a known namespace. However, you can still use the literal value Post. On the other hand, with enums, you can use it directly as a generic argument i.e. Post
You can pass `Post` instead and it would work, You can even make it nicer by making a type alias for `type X = typeof PostState` and now you can do it like this `Post` if you prefer. the only downside that you can't use the dot operator; so you have to do `X['Draft']` instead of `X.Draft`.
Yes you shouldn't use them; JavaScript Symbols exist for that reason (according to MDN) for instance: ``` const Colors = { RED: Symbol("❤️"), BLUE: Symbol("💙"), GREEN: Symbol("💚") } ```
@11:47 ok, it works......but.... I want to forbid the use of string here, I wish to force the use of PostState.Draft as a sngle source of truth. As a matter of fact, I was using enum and not union for that very reason. Any suggestions ?
It's been a while since I dabbled in Go! But Rust enums are absolutely THE BEST. I think the union type + exhaustive switch pattern in TypeScript took a lot of inspiration from Rust enums.
I think you can absolutely have both the constant and the type have the same name "PostState". Since one is a value and one is a type they won't collide. Typescript knows when you are using it as a value and when you're using it as a type. And it also gives the exact same DevX as Enums because an enum is both a type and a value.
5:00-5:45 Why the hell would you use numbers anyway if you can use human-readable form? Why purposely add obscurity to your own code... 7:00-8:27 Using strings is prone to typos and when you need to change the value, you have to do it in only one place. If you need to change the reference, you always know where it comes from. When you have an object that holds your constant values, why would you type in a value itself, when you can refer to that object? If you want to do otherwise, why to create enum at all? And what is wrong with importing from library? Isn't it a good thing if a library provides you with a more flexible (I mean, that can be used as a type and value) contract of using it? Imo, what you said about enums should be presented not as flaws, but as peculiarities. One should be aware of them, true.
I think enums are a mistake in TypeScript. It should be removed. Just like "interface" should be removed and its (very few) unique features should be included in "type". Unfortunately, the TypeScript group is too careful too often. Just remove it in the next major release. Most people won't care because most people aren't using it.
Can't we just get a lesson from C++ and have enum classes ? enum class State { Draft, Scheduled, Published, } // Nope const x: State = 0; // TS: Error, invalid value for State, should be `State.Draft`, `State.Scheduled` or `State.Published` // Still Nope const x: State = 3; // TS: Error, invalid value for State, should be `State.Draft`, `State.Scheduled` or `State.Published` // Nope Again const x: State = "Draft"; // TS: Error, invalid value for State, should be `State.Draft`, `State.Scheduled` or `State.Published` // Yep const a: State = State.Draft; const b: State = 1 as State; // Explicit cast const c: State = 5 as State; // Will work, but you shouldn't use this.
Yeah, other languages do it so much better. I’d love rust-style Enums in TS, where you can store values and do matching (you can kinda get this pattern with an exhaustive switch and union type in TS)
Do you have the same reluctance with classes ? Given that they have the same characteristics than enum, to be both value and type ?
hmm, good question! I don't typically use classes much, but if that's the pattern you're using, I think classes are fine. The nice thing about classes is that they are a type and a value in BOTH TypeScript and JavaScript, so the behaviour is pretty consistent: that is, you can do `myObj instanceof MyClass` in both TS & JS.
One thing to keep in mind is that JavaScript doesn't (yet) have private properties within classes, so any private properties in your TypeScript classes will be normally-accessible properties once transpiled to JavaScript.
edit: put together some quick examples here: shaky.sh/ts-private-properties/
Thanks to share your thoughts, Andrew !
Actually, I have difficulty to find a good use case for real classes. I use Java for a long time and JS for 5 years. It seems unnatural in TS, in part, because TS only need the shape of the object to consider the type.
Whatever, you have a point to mention the instanceOf operator available in both TS and JS worlds, pretty consistent indeed.
Nice article to private properties trick.
In JS and TS you can use closures instead of classes and that way you don't need to use the "this" keyword
2 years old and TS still hasn’t fixed these “issues”. Love the video!
I’ve used enums to great effect in my project. In regards to accept user input and start using it directly in relation to a string enum, I think that in itself is a vad practice and you should always sanitize and qualify the input anyway to see if it matches the enum. So I actually like TS insistance on making devs explicitly use the enum instead of passing in the string.
your alternative became even better with typescript declaration merging which allows us to use the same name for the value and type
Such a clear and thorough explanation of the enums and the alternative! I finally get it. Thank you, Andrew!
Great video!
After watching I started thinking, and this is the result:
export type Enum = T[keyof T];
const ABC = {
A: 'a',
B: 'b',
C: 'c'
} as const;
let X: Enum = ABC.B;
With my full respect :
I removed "extends Object" from and it still works.
Any idea why?
The last example is what I ended up doing myself many times. Super frustrating to work with enums. Sometimes they work fine, other times you just end up regretting using them.
This "as const" technique is 🔥 don't know how I didn't come across it earlier, thanks for sharing!
Also worth noting that in your example, you could have just named your type "PostState" and the const "PostState" instead of "PostStateType" which is more awkward. Because of declaration merging, this is allowed.
First video and I NEED to subscribe. Awesome discussion, Andrew! I look forward to learning from you and suggesting your channel to other devs.
I don't subscribe a lot but i am subscribed to you hats off to Andrew great tutorial
Still new to TypeScript, but at this point, why not just set the type for PostState as a union of strings ("DRAFT" | "SCHEDULED" | "PUBLISHED")? There's a lot of mental overhead for me to process what you used in the end.
You still get auto completion. Anything else this doesn't get you? Or is it purely the look of raw strings vs. the more contextual syntax that repeats the PostState part in PostState.Draft e.g.?
You're absolutely right. You don't get anything more than the PostState.Draft object notation vs the "DRAFT" string literal. To me this is purely a code aesthetic choice. When self documenting code is important, especially with multiple developers writing library code with the same enum, then Andrews pattern is nice. But its often overkill.
I look forward to seeing your channel growing my fren! awesome content
🤯🤯Shocked to see such low subs? absolute great content.
Are there any good use cases for enums beyond when you essentially want named numbers? I used to use them liberally but then found just using unions of strings way more ergonomic.
I haven't come across any cases where a unions of strings doesn't make sense.
@@andrew-burgess renaming
@@igorswies5913 hmm, can you say more? not sure I follow.
@@andrew-burgess I don't think you can rename a string from a union using F2 and have it be updated everywhere, can you?
@@igorswies5913 Oooh, gotcha! Yeah, that's a good example!
The best you get in that case is that after you update the union, the type checker will tell you all the places that no longer match the union. But you'll have to update them manually :(
Very clear explanation. Thanks for sharing.
They are the Devil incarnate! LOL I never use them. Thanks for sharing.
In which scenario would using the same name for the type as the variable be a problem? Surely the type names don't have any relevance on the variable names in typescript, isn't the language always lexicographically disambiguous in whether you're refering to a type or a variable?
as far as im aware, yes. i do that all the time lol. very useful as an export
I use a very similar solution, but instead of "as const", I wrap my object literal with Object.freeze, which has the same TypeScript benefit of "as const" and also protects your object at runtime from someone making changes to it.
Object.freeze won’t protect object modification when it has nested attributes
I ran into an interesting problem with this pattern.
If you want to use an enum-like for a generic argument, an actual enum will work, but the as const version will not.
If you try to pass Post, it will complain that PostStateType is not a known namespace. However, you can still use the literal value Post.
On the other hand, with enums, you can use it directly as a generic argument i.e. Post
You can pass `Post` instead and it would work, You can even make it nicer by making a type alias for `type X = typeof PostState` and now you can do it like this `Post` if you prefer. the only downside that you can't use the dot operator; so you have to do `X['Draft']` instead of `X.Draft`.
```
type PostStateK = typeof PostState
type PostStateV = PostStateK[keyof PostStateK]
interface BetterPost {
id: number
state: State
}
const y: BetterPost = {
id: 2,
state: 'DRAFT'
}
```
Awesome video. helped me allot.
great tutorial, thanks!
Yes you shouldn't use them; JavaScript Symbols exist for that reason (according to MDN)
for instance:
```
const Colors = {
RED: Symbol("❤️"),
BLUE: Symbol("💙"),
GREEN: Symbol("💚")
}
```
@11:47 ok, it works......but.... I want to forbid the use of string here, I wish to force the use of PostState.Draft as a sngle source of truth. As a matter of fact, I was using enum and not union for that very reason.
Any suggestions ?
use const enums
why is there no link from the code?
Go kinda avoids enums I use them in strapi but generally kinda keep away from em
It's been a while since I dabbled in Go! But Rust enums are absolutely THE BEST. I think the union type + exhaustive switch pattern in TypeScript took a lot of inspiration from Rust enums.
🙏🙏
nice blooper at the end there :D
😂
I think you can absolutely have both the constant and the type have the same name "PostState". Since one is a value and one is a type they won't collide. Typescript knows when you are using it as a value and when you're using it as a type. And it also gives the exact same DevX as Enums because an enum is both a type and a value.
the best!
Its funny how TypeScript refuses to add some QoL because it would affect the resulting JS code and then enums exist lol.
Good video, annoying music. Subscribed.
Thanks! Yeah, I’ve gotten feedback on the music, and have been (I hope) improving that.
Honestly typescript doesn't really give any feel of a strongly typed language. It also adds to the complexity.
Ooh, that's a take I haven't heard before: that TS isn't strongly-typed. Say more?
why even bother with enums or object as consts if you can just use union types of specific values?
Refactoring is a bit easier if you can search for the enum/object name
5:00-5:45 Why the hell would you use numbers anyway if you can use human-readable form? Why purposely add obscurity to your own code...
7:00-8:27 Using strings is prone to typos and when you need to change the value, you have to do it in only one place. If you need to change the reference, you always know where it comes from. When you have an object that holds your constant values, why would you type in a value itself, when you can refer to that object? If you want to do otherwise, why to create enum at all?
And what is wrong with importing from library? Isn't it a good thing if a library provides you with a more flexible (I mean, that can be used as a type and value) contract of using it?
Imo, what you said about enums should be presented not as flaws, but as peculiarities. One should be aware of them, true.
I think enums are a mistake in TypeScript. It should be removed. Just like "interface" should be removed and its (very few) unique features should be included in "type". Unfortunately, the TypeScript group is too careful too often. Just remove it in the next major release. Most people won't care because most people aren't using it.
Can't we just get a lesson from C++ and have enum classes ?
enum class State {
Draft,
Scheduled,
Published,
}
// Nope
const x: State = 0; // TS: Error, invalid value for State, should be `State.Draft`, `State.Scheduled` or `State.Published`
// Still Nope
const x: State = 3; // TS: Error, invalid value for State, should be `State.Draft`, `State.Scheduled` or `State.Published`
// Nope Again
const x: State = "Draft"; // TS: Error, invalid value for State, should be `State.Draft`, `State.Scheduled` or `State.Published`
// Yep
const a: State = State.Draft;
const b: State = 1 as State; // Explicit cast
const c: State = 5 as State; // Will work, but you shouldn't use this.
Yeah, other languages do it so much better. I’d love rust-style Enums in TS, where you can store values and do matching (you can kinda get this pattern with an exhaustive switch and union type in TS)