[HN Gopher] TypeScript Features to Avoid ___________________________________________________________________ TypeScript Features to Avoid Author : gary_bernhardt Score : 212 points Date : 2022-01-18 20:19 UTC (2 days ago) (HTM) web link (www.executeprogram.com) (TXT) w3m dump (www.executeprogram.com) | dmarchuk wrote: | I've never had any issues with enums, namespaces or private | keywords (can't speak for decorators as I haven't used them yet). | Although I do understand the reasoning for using the | '#somePrivateField' rather than 'private somePrivateField'. | | But features like enums are part of the reason why we even have | tools like TypeScript, because JavaScript lacks these features. | wdb wrote: | I wish TypeScript somehow would use #-private fields underwater | when compiling code. `#field` is pretty confusing; I keep | thinking it's a comment and it looks ugly | dmarchuk wrote: | Although I do understand the reasoning in the article, I | completely agree with you. | | Switching between Python and TypeScript/JavaScript all the | time, using '#' to define private field feels weird and I | personally prefer more explicit way of writing code. Plus | AFAIK the 'private someField' is common in other languages | (Java, Scala,...). | samwillis wrote: | What I like about TypeScript is that all these features are | optional, declaring that they should be avoided is going too far, | bus so would be saying that you should always use all features of | a language if it could apply. | | Everything has its place, and for allot of the features of | TypeScript I think they are designed to be useful when you have a | large number of developers working on incredibly large codebases. | | I suppose in some ways it's like C++, you can decide to fully | embrace all of its features, or code in a much more C like way, | just taking advantage of classes. | | It comes down to personal/team preference, what works for you. | | Personally with TypeScript I'm inclined to code in a "closer to | JavaScript" way (but taking full advantage of types obviously), | but would happily work in whatever style was prevailing in the | project. | chrismorgan wrote: | For people that don't see the problem and are happily using these | features, here's an explanation of the _second_ problem with | these sorts of features, additional to the "it's not just | JavaScript with types which is what the label said" reason which | is the focus of the article. | | The real trouble occurs when TypeScript implements something | because that looks like the way things are heading, but then they | don't head that way, and JavaScript and TypeScript diverge | _incompatibly_. The type stuff is generally fairly safe, and | fundamentally necessary, but some of the other stuff they've | added isn't safe and isn't... _as_ necessary, at least. | Decorators are the current prime case of this divergence problem: | they added a feature because it was useful, lots of people wanted | it, and that was what people expected JavaScript to get before | long, and some were using already through a Babel extension but | people were sad about having to choose between nifty features | (Babel) and types (TypeScript); and then because they'd added | something not in JavaScript, why not go a bit further? and so | reflection metadata came along; and then... oh, turns out | decorators are actually heading in a completely different and | extremely incompatible direction in TC39 now, but people are | depending on our old way and PANIC! It's been a whole lot of | bother that will continue to cause even more trouble, especially | when they try to switch over to the new, if it gets stabilised-- | that's going to be an extremely painful disaster for many | projects, because "experimental" or not, it's a widely-used | feature of TypeScript. | | This is not the only such case; there's one other that caused a | lot of bother comparatively recently, but I can't think what it | was (I don't work in TypeScript much). | | Sciter used what was loosely a fork of JavaScript when it | started, and diverged, for quite decent reasons in some cases I | will admit, but this divergence caused more and more trouble, | until recently they gave up and switched to JavaScript, ... | except with a couple of incompatible deviations already and more | on the table as probabilities. Sigh. I had hoped a lesson had | been learned. | | So yeah, I'm content to call these things misfeatures. TypeScript | overstepped its bounds for reasons that seemed good at the time, | and may even have been important for social reasons at the time, | but you're better to avoid these features. | gherkinnn wrote: | I recently listened to an interview with Igor, Angular's | inventor. | | In it he talked about how Angular 2 pushed decorators in to TS. | And to this day, Angular is the only major JS thing that I can | think of that uses decorators. | | Creating that ng-abomination was not enough. No. Google also | had to poison a perfectly fine language. | holler wrote: | The latest versions of Ember.js (Octane) have built-in | decorator support and they're discussed in the RFC: | | https://github.com/emberjs/rfcs/blob/master/text/0408-decora. | .. | | https://guides.emberjs.com/release/in-depth-topics/native- | cl... | ameliaquining wrote: | Did the other feature have anything to do with modules? If so, | I can't really blame TypeScript too much for that, since the | JavaScript ecosystem is pretty fragmented there and TypeScript | has to support all the different things that are in use with a | reasonable interop story. Build tools that work purely with | JavaScript source code also have to deal with this problem. | | Otherwise I'm not aware of any cases besides decorators where | TypeScript did something that was incompatible with a later | ECMAScript development. | chrismorgan wrote: | I really don't remember, sorry. I just scanned through | https://github.com/Microsoft/TypeScript/wiki/Breaking- | Change... and nothing jogged my memory; I'm wondering if it | actually _was_ something to do with decorators, though I | still think _probably_ not. I investigated a bit at the time | because I was looking into using TypeScript on a certain | project and the changes would probably affect me, but I | wasn't _actually_ using TypeScript at the time, and it didn't | stick in my memory. Something about some change being spread | over a few versions with deprecation followed by a silent | breaking change in the generated code, but I can't remember | what. | EMM_386 wrote: | I disagree with this. We have a reasonably large Angular | application that is only going to get much bigger (hard to define | what that means ... big telephony app with tens of thousands of | customers). I am the lead and architect. | | We use enums and private keywords. With the private keywords, all | I care about is that it is logically correct. We use private when | things are truly private, i.e. they are only called from within | the same class and don't need to be made visible to the view | template or outside of the class. I honestly don't care _what_ | this transpiles down to, the point for us at least is not to make | things "truly private" (good luck with that in JavaScript). It's | simply to compiler-enforce rules. We also have ESLint to ensure | that our private fields are all below the public ones to keep | things nice and neat. | | I also enforce that we actually make things as public although | that isn't needed, and I enforce returning void. | | So instead of: | | someMethod() {} | | I have us use: | | public someMethod(): void {} | | Just to state what you intend. | | I realize "I don't care what this transpiles down to" might | really irk some people, but I really don't. In our C# back-end I | am much more strict about this stuff, but in JS at the moment | given the standardization of the #private fields and the fact I | consider them really ugly, I honestly don't care. Just give me a | clean code base that enforces we can't reference private fields | and methods from our templates. | | For enums, I recently wrote a method that does exactly what this | article says not to do, use it for GET, POST, and PUT. | | What would be a _cleaner_ way to write this? If it has to be | refactored into something much "uglier" I don't think I'd prefer | it. this.downloadService.fileWithProgress(url, | RequestType.post, ActionType.download, body) | | This is a service I wrote that handles real-time progress (i.e. | show an accurate progress bar when downloading files). I think | this is clean and logical. | colejohnson66 wrote: | > I honestly don't care what this transpiles down to, the point | for us at least is not to make things "truly private" (good | luck with that in JavaScript). It's simply to compiler-enforce | rules. | | It's not just JavaScript. With reflection in C# and Java, you | can mess around with private variables from outside the | classes. For Java, this can have some pretty interesting | results, such as _2+2 being equal to 5_.[0] The whole point of | compiler level annotations is to keep good programmers honest. | | If some devious JavaScript developer wants to ruin your | library, that's their fault. | | [0]: https://codegolf.stackexchange.com/a/28818/13944 | pfooti wrote: | I mean, I don't know if you can do this with modern c++, but | I would occasionally do this when I was being aggressive in | my c++ tweaks: #define private public | #import <something.h> | | then you can interact with your class private fields all you | want. | EMM_386 wrote: | Agreed, with reflection you can get around that also, but in | JS it's even less of a "real thing" at the moment and I don't | see that changing until the far future where we can drop some | of this legacy cruft. | lmm wrote: | > With reflection, you can mess around with private variables | from outside the classes. | | Worth mentioning that you can disallow reflection via the | security manager, at least in earlier versions of the JVM. | BrandonM wrote: | From the article: | this.downloadService.fileWithProgress(url, 'POST', 'download', | body); ... public | fileWithProgress(url: string, reqType: RequestType, actionType: | ActionType, body: ...): void { ... } | ... export type RequestType = 'GET' | 'POST' | ...; | export type ActionType = 'download' | ...; | 015a wrote: | The only point I take slight issue with is Avoiding Namespaces. | | I can't speak for frontend code, but on the backend: | | It feels at least somewhat obviously true that unique names are | good. Even if you aren't operating in a language which has global | imports (which is most nowadays), unique-as-possible names can | help disambiguate-at-a-glance something like: | const user: User = await getGoogleSsoUser(); // 100 lines | later... console.log(user.microsoftId); // wait why isn't | that field available? // ok i'll fix this const | googleUser = await getGoogleSsoUser(); // obviously that | makes sense; but what about the type? export function | getGoogleSsoUser(): Promise<User> {} // wait... should | that return a Google API user object? or our own user model? | // let me scroll 200 lines up, ok its defined there, open that | file... // or just: export function | getGoogleSsoUser(): Promise<GoogleUser> {} | | Contrived example of course, but it's a broader pattern I see | every day; there's a lot of overloaded terminology in | programming. | | But this gets hairy really quickly. // google | will provide these types... but lets assume you're writing your | own export type GoogleUserV1 = ...; export type | GoogleAPIV1GetUserResponse = { user: GoogleUserV1, | } export type GoogleAPIV1ListUsersResponse = { | users: GoogleUserV1[], count: number, } | // ok lets import them import { GoogleUserV1, | GoogleAPIV1GetUserResponse, GoogleAPIV1ListUsersResponse } from | "my/service/google"; | | First, the type names get really long, which makes them hard to | read at a glance. Second; this cost is replicated anytime someone | wants to import something. Third, they oftentimes become a | seemingly randomly ordered set of words written like a sentence; | why is it not "GoogleV1APIListUsersResponse" or | "GoogleAPIListUsersV1Response"? | | We can solve the second problem by doing an old-style wildcard | import: import * as google from | "my/service/google"; const user: google.GoogleUserV1 = | await getGoogleSsoUser(); | | But this almost always ends up stuttering, because the producer | package still wants to guarantee unique-as-possible exported | names, as asserted above. So we made problem 1 worse, and did | nothing for problem 3. | | With namespaces: export namespace Google { | export namespace User { export type V1 { ... } | export type GetResponse { ... } export type | ListResponse { ... } } } // now to | import it import { Google } from "my/service/google"; | const user: Google.User.V1 = await getGoogleSsoUser(); | | The symbol, as a whole, isn't shorter. But, it's easier to read | (and write!). It also helps disambiguate where in the symbol each | component of the type's name should reside, when the producer | wants to for example add a new type or function. | | The argument against presented by the article boils down to: it | creates unnecessary fluff in the emitted javascript. That's a | reasonable argument; it does. In practice, it's more nuanced. | First: I've never seen it cause an issue. So, premature | optimization, YMMV, etc. Second: the fluff is erased for types | anyway; so it only becomes an issue for functional code defined | like this (all of my examples were in types, but its easy to | imagine a Google.User.List function). Third, though not a direct | counterargument to the article: it's literally how Google | organizes the types we've been talking about [1] (though, how | they organize the functional code, I'm not sure). | | [1] | https://github.com/DefinitelyTyped/DefinitelyTyped/blob/mast... | leodriesch wrote: | Despite the large amount of criticism in the comments here I | think that the point the article makes here is pretty valid. | | The described features are not what TypeScript itself wants to | be, and I think if it wasn't for backwards compatibility the team | would remove some of them. | | IIRC namespaces as well as the `import = ` syntax come from a | time where ESM wasn't a thing yet but a module system was very | much needed. So now that ESM can be used pretty much everywhere | namespaces can be avoided and therefore reduce the learning | surface of TypeScript. | | Enums IMO have no advantage over union types with string | literals, e.g. `type Status = 'fulfilled' | 'pending' | | 'failed'`. They map way better to JSON and are generally easier | to understand, while still benefiting from type safety and typo | protection. | | Well the private keyword is kind of like namespaces in that it | came from a time where the feature was needed/wanted, but the | EcmaScript spec was not moving fast enough. So they made their | own version of it which is now obsolete. | | And for decorators, IIRC the decorator spec has already moved on | and TypeScript's implementation is no longer up to date with it. | And the spec itself is only stage 2 as mentioned in the article, | so I wouldn't recommend using decorators either, you will face | breaking changes with them some time in the future. | | Furthermore, it is far more likely that you run into trouble when | using one of these features with a compiler that is not TSC, e.g. | esbuild or Babel. Decorators have never been working all that | well with the Babel plugin. Enums are probably fine here. | ameliaquining wrote: | Numeric enums are a lot more useful than string enums, in my | view. | goodoldneon wrote: | Just don't use numeric enums with implicit values. If you add | a member anywhere but the end of the enum, it'll change | existing members' values | leodriesch wrote: | I like string enums, since they are self-documenting, I guess | int enums are smaller when sent over network. Could you | expand on that? | spoiler wrote: | Why do you find them self documenting? | | Usually it's just enum Method { | Get = "GET" // ... } | | I usually always put doc comments on enums and their | variants. | | Regarding why numeric ones are (only sometimes) more useful | than string values: bit flags comes to mind, faster | comparison of numbers than strings (not always tho... | unless this is a misconception, but I don't believe so), | you mentioned smaller bundle size already. | | As for "auto" enums: the fact they're numbers now is an | implementation detail. They could be unique Symbols in a | future versions of typescript. You can do that manually now | too, but I'm talking about the automatic (without | assignment) syntax. | | Regarding article: I... Half-agree. But I'd not completely | disregard/avoid enums. At the very least, they can be | useful to help model some behaviours/states in more | readable, accessible, and coherent way (Other comments went | into a more in-depth defence of enums, thought). | KuhlMensch wrote: | enum Method { Get = "method/GET" // | ... } | | The string value can be very helpful for the reader - be | it a human or log ingestor | ameliaquining wrote: | In the use cases where I most typically use enums, I don't | want to think about the question of runtime representation; | I just want a set of arbitrary symbols that are different | from one another. Implicitly initialized numeric enums do | this idiomatically and concisely. | leodriesch wrote: | Good point, I'm mostly doing web stuff so I always think | about how something looks in JSON in the network panel of | my browser. | | If you just use the value within your code the runtime | representation does not matter, you're completely right. | piaste wrote: | If you truly don't care about the runtime representation, | then it sounds the idiomatic JS/TS construct you actually | want is Symbol() | | https://developer.mozilla.org/en- | US/docs/Web/JavaScript/Refe... | | https://www.typescriptlang.org/docs/handbook/2/everyday- | type... | JohnHaugeland wrote: | Symbol is an extremely heavy hammer. It's also difficult | to use correctly. | | What value do you think it offers here? | ameliaquining wrote: | I agree that it might have been better for that to be the | TypeScript idiom, but it's not, due to the longer- | standing popularity and concision and language-level | support for enums. (Which couldn't have originally been | designed to be lowered to symbols, because at the time | symbols weren't widely-supported enough.) | rezonant wrote: | Symbols are very well supported in TS today. You even get | proper type enforcement and intellisense when using | symbols as the names of methods and properties, for | instance. Whether you should use them for enum-like | purposes or not has more to do with how you want to use | the values though. For instance using symbols for | defining numeric protocol values doesn't make sense. Same | for HTTP methods, where you are always ultimately passing | a string down to the http client/server interfaces | iLoveOncall wrote: | > Enums IMO have no advantage over union types with string | literals, e.g. `type Status = 'fulfilled' | 'pending' | | 'failed'`. | | Well I would say having to not repeat and update your code | everywhere when you change or add a possible value is a pretty | big advantage. | pjerem wrote: | My IDE does this just fine (WebStorm, btw) | The_rationalist wrote: | [deleted] | Scarbutt wrote: | Though I have never seen anyone till now call TypeORM a fine | choice. | kesslern wrote: | One advantage of enums is that you can iterate over all | possible values of an enum, but not a union type. | cobertos wrote: | You can if you derive the union from a const array using | indexed types. const MyTypeValues = ['a', | 'b'] as const; type MyType = typeof | MyTypeValues[number]; | | MyType is now a type 'a' | 'b' | lucasyvas wrote: | This is the way, because iterating enums produces odd | results due to a bidirectional mapping. | | I had always used enums in TS until this year, but union | literals are better. | | I create my own enums with const objects, compute the type | based off the object's values. So very similar this, just | with an object as the source instead of an array. | sli wrote: | Iterating over an enum in TypeScript always felt like | code smell to me because of the filtering code I'd have | to write to deal with the bidirectional mapping. | jbbe wrote: | I often use that approach but (especially when you're | importing from a seperate package) the compiler will | sometimes view MyType as just an alias for string and won't | catch typos. | | Am I missing something in how to use this? | Recursing wrote: | `as const` is the important bit there, see this | playground link https://www.typescriptlang.org/play?#code | /MYewdgzgLgBKlQIICd... | ragnese wrote: | So... You have two declarations, one of which is a real | array that's allocated at runtime. Is that really better | than an enum? | | Not to mention, there's a _slight_ mental overhead to | parsing this. When I see this code, I might wonder if there | 's a reason for this to be an array. I might wonder if the | order is intentional. | | An enum has a more clear intent. My only complaint is that | enums are not string-by-default, so we end up writing our | variants twice: enum MyType { | A = 'a', B = 'b', } | nfw2 wrote: | One minor advantage is that you can import the type on | its own. If an external app just needs the types, it can | import them without affecting its bundle at all. | Arnavion wrote: | >one of which is a real array that's allocated at | runtime. | | To be clear, the enum is also defined at runtime. So this | specifically isn't a difference. | ragnese wrote: | Yes, I didn't intend to imply otherwise, but I could've | elaborated. | | Some people will argue a preference for string literal | union types over enums because the string literal types | don't have any runtime overhead. They just provide type | safety at write-time and are bare strings at runtime. But | as soon as you start adding arrays and custom type | predicate functions to work with them, you're adding | runtime objects, which removes that particular advantage | over enums. | JohnHaugeland wrote: | > Is that really better than an enum? | | Substantially. Look at the generated code for an enum. | | Also, this approach does not suffer the problems | described by the article. | ragnese wrote: | > Substantially. Look at the generated code for an enum. | | I'll give you that. It looks like the TS compiler | (according to the playground site) spits out some code | that's intended for maximum compatibility with older | versions of JS, even when targeting newer versions (which | makes sense, since nothing is technically wrong about | it). | | It spits out: "use strict"; var | MyType; (function (MyType) { | MyType["A"] = "a"; MyType["B"] = "b"; | })(MyType || (MyType = {})); | | when, we would obviously write the following in modern | JS: "use strict"; const MyType | = { A: "a", B: "b", }; | | So that's a bit disappointing. | | So, this could matter if you intend to actually read the | emitted JS. If, however, you're TypeScript-only, this is | more-or-less the same as reading the ASM spit out by your | C compiler or the Java bytecode spit out by javac. | | > Also, this approach does not suffer the problems | described by the article. | | This argument doesn't hold water, unless you're taking a | philosophical stance. The argument is that most | TypeScript features don't actually spit out JavaScript | code and this one does. | | But, if you're going to write an array that lists your | variants (and then write code elsewhere to check if a | string is contained by said array, etc), then "extra" | JavaScript code is still being generated- it's just | generated by _you_ instead of the TypeScript compiler. | Why should we care _who_ generates the code? | | This argument only works when we're comparing to writing | a string literal union type _and no other supporting code | for that type_. My comment was specifically addressing | the case of writing an array to hold our literals instead | of writing an enum, and I stand by my claim that an enum | is better because it 's the same runtime overhead, but | more clearly communicates intent/semantics to your fellow | TypeScript devs (including future-you). | JohnHaugeland wrote: | > > Also, this approach does not suffer the problems | described by the article. > > This argument doesn't hold | water, unless you're taking a philosophical stance. | | Respectfully, philosophy has nothing to do with this. | | The argument that the other person made does, in fact, | hold significant water. There are extremely long | discussions about it on the Typescript GH repo. | | . | | > The argument is that most TypeScript features don't | actually spit out JavaScript code and this one does. | | No, it isn't. | | . | | > then "extra" JavaScript code is still being generated | | I never said extra code was a problem. I have no problem | with this. | | What I said was that I found the code emitted by the | enumeration stack to be problematic. You seem to have | inferred cause (incorrectly.) | | . | | > Why should we care who generates the code? | | Do you believe that I think a compiler should not | generate code? | | I never said anything of that form. | | Genuinely, it's difficult to hold a discussion with | people who read so deeply between the lines that they | come to bizarre conclusions, then think those conclusions | belong to the person on the other end of the wire. | | . | | > This argument only works when we're comparing to | writing a string literal union type and no other | supporting code for that type. | | You're not talking about the same argument that I am. | | . | | > but more clearly communicates intent/semantics to your | fellow TypeScript devs (including future-you). | | I write documentation. | [deleted] | dgb23 wrote: | > It may be a bit annoying to create many small files, but | modules have the same fundamental functionality as namespaces | without the potential downsides. | | Do they? | | It seems to me that namespaces are more powerful and convenient | than JS modules as they enable more structure. | | I have only dabbled in TS and am not sure how useful they are | there. But I assume they would be similar to PHP/C#/Clojure | namespaces on a conceptual level. | Tabular-Iceberg wrote: | My problem with enums isn't so much that it breaks type-level | extension, it's that it breaks structural typing. | | I don't particularly care what the generated JS looks like when I | do all my work in TS, but I do care that the type system of the | language I do work in works in a consistent manner. | cherryblossom00 wrote: | I find string enums useful when I want nominal typing. However, | I do think that it is a little confusing when most of the type | system is structural, except for string enums. What complicates | things further is that any number can be assigned to a number | enum without casting, which I understand is so enums can | represent bitfields (e.g. so Permissions.Read | | Permissions.Write is still of type Permissions instead of | number), but I wish numeric enums worked the same as string | enums and doing bitwise operations on these enums would return | the enum type and not a type-erased `number`. | | I mostly agree with the rest of the article's recommendations, | but don't fully agree with their reasoning. | | - Yes, namespaces should be avoided, but not because they | generate extra code. Namespaces are not recommended anymore and | modules (actual JS modules not TS' `module`) is the recommended | approach now. | | - Yep, private fields are better for ensuring a field is truly | private. | | - I think decorators are fine to use if you're prepared for | your code to potentially break in the future if they're | standardised. Developers use new/unstable features all the time | (e.g. stage 0/1 JS proposals via Babel, nightly Rust | toolchain). And I don't believe the lack of standardisation of | decorators should be a factor in deciding whether to use a | library that requires these. | mikojan wrote: | As long as you are not using "const enums" JavaScript/TypeScript | interop is not a real problem at all. | | The private keyword is obviously preferable to "#" in | environments in which you are targeting older ES versions. | fuzzy2 wrote: | But const enums are gone after compiling. They're just | namespaced constants that are inlined. I don't see how this | could cause any problem. | mikojan wrote: | That's precisely the problem. It makes the enum invisible to | JavaScript users. | fuzzy2 wrote: | Yes, as it should be. There are no enums. Instead, you can | pass in any of the documented strings (or whatever the base | type is). Even inside TypeScript, an enum is really just a | union type on steroids. | | When you expose TypeScript code to JavaScript consumers you | absolutely must validate all incoming data anyway, whatever | type you declare. | mikojan wrote: | This is not about trusting the information your users | provide. It is about making your APIs accessible to | users. If you hide some information, your API is less | accessible. | fuzzy2 wrote: | But it's just as hidden either way. The API consumer does | not know there's an enum that's supposed to go in there. | The only way to get that information is to read the | documentation. | davedx wrote: | The arguments not to use them are implementation details of the | tooling. | | Well designed Abstractions are good, not bad... | matt7340 wrote: | I like these recommendations, especially around decorators, maybe | I'll start following them | | _reviews Angular app_ | | oh | leodriesch wrote: | Nest also does this, seems very strange to bet on such an | experimental feature. It's very convenient to use though. | Vinnl wrote: | This basically comes down to: avoid TypeScript features that | clash with TypeScript's design goals. Specifically, the one to: | | > Avoid adding expression-level syntax. | | https://github.com/Microsoft/TypeScript/wiki/TypeScript-Desi... | | And that makes sense to me. IMHO it's best to consider TypeScript | as a tool that aims to help you write JavaScript by catching | common errors; it's more like a linter than a separate language | in that regard, and is what sets it apart from something like | CoffeeScript, and helps it avoid falling into the same traps. | msoad wrote: | Yes, I think if they could the would remove namespaces and | enums | wk_end wrote: | > it's more like a linter than a separate language | | I wish people wouldn't think like this; it's very possible to | write perfectly acceptable JavaScript that's fundamentally | terrible TypeScript. By "fundamentally terrible", I mean | unnecessarily untypeable, or unnecessarily difficult to type. | If you're just writing your usual JavaScript without thinking | about the types just figuring you'll "lint" it later with tsc | to catch some bugs, if you aren't thinking in TypeScript from | the jump, you're likely to make your life much more unpleasant | down the line. It's similar to the relationship between C and | C++. | baryphonic wrote: | > 1. Avoid Enums | | What? This is IMO bad advice. Having a sum type is quite handy | for general type-checking, at least insofar as the type truly is | an enumerated type (i.e. all possible values are known at design- | time). There have been times when TypeScript enums have been | indispensable to me when declaring the external interface to some | client-facing API. Whatever the API boundary is, a sum type is | useful. | | Also, TypeScript gives general intersection types, which is quite | rare among its peers. (What I wouldn't give some days for mypy to | have intersection types, or bivariant functions, or conditional | types, or ...) | | The only other impetus for this post I can imagine is some weird | desire to see typescript as a strictly separate layer above | JavaScript that "could be removed" if we wanted it to be. I | suppose that was the project's original telos, but today the | abstraction is leaky in a few places. I'm a world where JSX is | common and radically departs from what would be considered normal | JS, I don't see a problem with TypeScript being leaky here and | there. Hell, I'd prefer TS to be leakier and add opt-in runtime | checking (i.e. code gen), because it would make my life easier in | certain instances. | shadowgovt wrote: | Based on how two of the four points key into it, I think the | author is stuck using a tool in their tool chain that doesn't | support TypeScript and has been bitten by that tool doing | something Byzantine in response to TypeScript-generated code. | | That's a problem a lot of developers won't have. I've covered a | lot of ground without ever finding a tool that either hasn't | been retrofit to support TypeScript or that has issues that are | tickled by TypeScript generated code. My blunt recommendation | if somebody hits that problem is to find a better tool. | jannes wrote: | The TypeScript ecosystem is bigger than just the official | compiler, though. Many projects use alternative compilers like | esbuild or @babel/preset-typescript for their main pipeline and | only use the official TypeScript compiler as a typechecker | (with `--noEmit`, during CI). | | It's true that enums are harder to support for alternative | compilers. Especially `const enum` seems to be harder. | jcelerier wrote: | yes, we should also restrict every C++ feature to what | Borland Turbo C++ 3 supports too, since there are still | school that teach that making it part of the C++ ecosystem. | | Actually, as Amish communities are part of the human | ecosystem, maybe we should also restrict anything we build in | the real world to something that can be useful to the Amish | and stop building anything requiring a power grid ? | oefrha wrote: | > It's true that enums are harder to support for alternative | compilers. | | As long as it is supported -- and at least esbuild does, who | cares (as a user). Should I start avoiding every JS feature | that is hard to implement in alternative JS runtimes? | jannes wrote: | I don't know why you should care if it doesn't affect you. | | At least TypeScript cares enough to have added the | `isolatedModules` and the `preserveValueImports` flags: | | https://www.typescriptlang.org/tsconfig#isolatedModules | | https://www.typescriptlang.org/tsconfig#preserveValueImport | s | activitypea wrote: | What are the advantages of enums over a string literal union? I | can only see disadvantages. | brightstep wrote: | In some cases you can use it to define string arguments to an | external library. Maybe table names to an ORM or a well-known | file path. This helps because you get autocomplete from | typing `MyEnum.` and seeing options, even though the external | function takes a plain string. | afavour wrote: | All of that is possible with a string literal union though, | surely? | brightstep wrote: | It's not possible to get the autocomplete unless the | function/method you're calling takes a union. Many | libraries can't do that because they need to be flexible | in what they receive. So yeah, you can define a union, | but editors don't have the context to know your union | applies to the call. | DrJokepu wrote: | If you have to change one of the underlying values, you only | need to change it in one place. Of course, you could just use | constants, but then you'll just have a const enum with extra | steps. | [deleted] | ragnese wrote: | Unfortunately, neither (string) enums nor string literal | unions are supersets of each other when it comes to | functionality. But, IMO, string-only enums are generally | better. | | You can't test if a given string is an element of a union | without writing a custom helper and/or allocating a runtime | array of the values. | | On the other hand, if you don't need to test unknown strings, | then union types disappear at compile time, whereas enums are | compiled to real, runtime, objects. | | Enums don't look like naked strings in the code. Frankly, | it's just nicer on my brain to see `return Color.Red` than | `return "red"` and wonder if "red" is just some random text | or if it has semantic meaning. Hopefully your IDE is smart | enough to take you to where "red" is defined as part of a | union type when you want to see other options. Granted- the | most popular editors ARE smart enough to do that, but that | doesn't help when just reading code with my eyes instead of | my hands, or in patches/diffs. | dbrgn wrote: | Unfortunately TypeScript's enums have various shortcomings | compared to powerful sum types in languages like Rust: | | - https://stackoverflow.com/questions/40275832/typescript- | has-... | | - https://github.com/microsoft/TypeScript/issues/32690 | | In a TS codebase I'm currently working on, we have the policy | of never using "plain" TS enums. Instead, we have a tool that | generates our own enum objects using a schema and a generator | script for the cases where we want "rich" enums. For other use | cases, we use string unions a lot (in combination with a helper | function that allows exhaustive matching). | IshKebab wrote: | Rust can't do your second example yet either. Enum variants | are not distinct types. It's a commonly requested feature | though so hopefully soon... | eyelidlessness wrote: | The reason enums are useful--and the private keyword until | recently with JS adding private fields--are that they're nominal | types. You can have... enum HTTPMethod { | POST = 'post', // ... } enum | FenceMaterial { POST = 'post', // ... } | | ... and you can be sure 'post' is not ambiguous. | | Private fields have the same benefit, which is particularly | useful for treating abstract classes as nominal interfaces. But | yes, if your target environments support private fields natively, | it's more idiomatic to use those than the private keyword now. | | I _generally_ avoid namespaces, but they're also sometimes useful | eg satisfying weird function signatures expecting a callback with | additional properties assigned to it. This is of course uncommon | in TypeScript, but fairly common in untyped JavaScript and its | corresponding DefinitelyTyped declarations. | rezonant wrote: | Namespaces are most useful for external typings, and in some | cases are the only way to correctly model them, sadly | seniorsassycat wrote: | Lots of comments seen to have missed that the article isn't | recommending against private fields, only the private keyword. | | Typescript and JavaScript have a newer brand # to make fields | private. The native feature will have runtime benefits while the | private keyword creates fields that's are public at runtime. | | I still use the private keyword because constructor shorthand | doesn't support brands. constructor (private | field) constructor (#field) // error | ironmagma wrote: | > We'd like to maintain feature parity with JavaScript unless | there's a compelling reason not to. | | And ladies and gentlemen, the generated code... | | var __classPrivateFieldGet = (this && | this.__classPrivateFieldGet) || function (receiver, state, | kind, f) { if (kind === "a" && !f) throw new TypeError("Private | accessor was defined without a getter"); if (typeof state === | "function" ? receiver !== state || !f : !state.has(receiver)) | throw new TypeError("Cannot read private member from an object | whose class did not declare it"); return kind === "m" ? f : | kind === "a" ? f.call(receiver) : f ? f.value : | state.get(receiver); }; | | Nothing about this says "native JavaScript" to me. Who cares | that a private field is technically public at runtime? | robpalmer wrote: | You're looking at down-levelled code. Babel and esbuild would | equally produce similar code. This is not a TypeScript issue. | | If you want "native" JS output, use the tsconfig option... | "target": "esnext | shadowgovt wrote: | The author misses I think an important point regarding enums: | using a union type instead means you can't iterate the list of | valid values without restating them in the value domain in a non- | DRY fashion. The reason enum generates code is that it is both a | type construct and a value construct... That ends up being useful | in myriad contexts. | remorses wrote: | I would also suggest to avoid too complex genrics, these tend to | make te code unreadable and make the compilation super slow. | | Usually generic code tries to make everything type safe but | having some portions of your code be dynamic is completely fine | imo. | antihero wrote: | I disagree with the point about enums, they can be used to alias | otherwise inconsistent long strings with more readable, | consistent, succinct ones. For instance, product SKUs. I've had | absolutely no problems with them with either past or current | tooling (e.g. esbuild). | | They can also be used in the more traditional form to represent | some arbitrary values. | | They shouldn't be overused, though. | breatheoften wrote: | Is anyone still using the class keyword in javascript or | typescript? private field syntax doesn't matter in the first | place if you don't use class {} anywhere ... | | I feel like most of the typescript code i've been in recently | looked like it needed 0 more class declarations. | pjerem wrote: | Try using Angular without using the "class" keyword. | | (btw, don't try using Angular at all if you are already happy | with your career) | eyelidlessness wrote: | I use classes quite a lot. _And_ I'm kind of a FP zealot. I | generally treat class as a struct value type, and I also | generally go to the trouble of marking every property readonly. | They're useful for several reasons: | | - Abstract classes are useful for defining nominal interface | types | | - Classes are a clear signal that a set of methods are designed | to interact with related data types | | - The prototype chain can be helpful for debugging where POJOs | may lose information in the call stack | | - JS runtimes can sometimes optimize classes in ways they can't | with POJOs | arcosdev wrote: | Totally agree. JS has closures why do we need the private | keyword? | gmiller123456 wrote: | I use "class" all the time in Javascript (don't use Typescript | at all), why wouldn't you? | breatheoften wrote: | I think the oo features tend to not play as well with many | styles of functional programming -- at least the forms of it | that work well in typescript ... in typescript I tend to | represent data as structurally typed "plain old javascript" | objects and my program becomes largely just functions that | operate on values and return new values. The idiomatic ways | available in the language to copy and produce new values from | old values tend to be straightforward and without as many | gotchas when the values themselves carry all their meaning | without the prototype chain. Once the prototype chain is a | important factor in the program behavior you tend to have to | keep track of "how exactly was a value with this shape | acquired" -- i can't as easily just roundtrip it through some | json for example -- and copying a value tends to not be as | composable an operation with a tree of objects where the | reachable sub objects all have behavior based on prototype | chain. | | When the prototype chain is involved I think the value gained | from structural typing tends to decrease -- or at least | exposes the programmer to more sharp edges. | cageface wrote: | Also classes don't serialize nicely so they're a headache | when dealing with things like redux actions, api endpoints, | storing in local storage or a db etc. POJOs are a lot | smoother. | orta wrote: | Yeah, for sure, I agree with all of this. I think the best way to | write TypeScript is to really treat it like the tagline | "JavaScript With Syntax For Types" - some of the extras | TypeScript provided from before TC39 were active have much less | relevance now. | yCombLinks wrote: | He is completely incorrect about the purpose of enums. It isn't | to simplify changing all locations of an occurrence. It is for | defining domain concepts. It is to communicate to other code | users, here is every allowed permutation of this type. | KuhlMensch wrote: | I don't know if you are correct, but this is closest to how I | think about it | | Mechanically, a const dictionary, or a string union might | achieve the same. | | But semantically, an enum (sometimes) reads better. | seniorsassycat wrote: | You can do that with sum types. type methods | = 'a' | 'b' const value: methods = 'c' // error | | The difference is enums are also nominal, while most types are | structural. | joshstrange wrote: | After trying enums in TS a long time back I also realized it was | best to avoid them. Here is my solution to this: | // filename: role.ts export const Role = { | CUSTOMER: 'customer', ADMIN: 'admin', | SYSTEM: 'system', STAFF: 'staff', } as const; | type TRole = keyof typeof Role; export type TUserRole = | typeof Role[TRole]; | | Using this structure I can reference any of my roles by | `Role.CUSTOMER` and the value is `customer` because it's just a | `Record<string, string>` at the end of the day. But I am able to | type things by using my `TUserRole` so a function can required | that the input be one of the values above. For me this is really | clean and easy to use. Note the `type TRole` isn't exported as | it's only an intermediary in this process, I could just as well | name it `type temp` in all my files and never worry about | conflicts. | | This way I'm not spreading "special" strings all over my code | (always a sign of code smell for me), I can changed everything at | a central location, and it's valid JS (it's just an object). | | EDIT: I know that `as const` seems unnecessary but I'm pretty | sure it's needed for some reason, I whittled this down to the | smallest/simplest code block I could and I just copy/paste this | pattern whenever I need enum-like functionality. | conaclos wrote: | `as const` is needed for ensuring that TypeScript uses literal | types for the enum members. Otherwise you ends with `TUserRole | = string`. | cypressious wrote: | Have you tried `const enum`? | | See | https://www.typescriptlang.org/docs/handbook/enums.html#cons... | | > Const enums can only use constant enum expressions and unlike | regular enums they are completely removed during compilation. | Const enum members are inlined at use sites. | ragnese wrote: | I remember reading somewhere that the TypeScript devs | considered const enums to be a mistake and recommend against | using them. I don't remember why, though. | russellsprouts wrote: | const enums are one of the few cases where type information | changes the emitted JS, something that's arguably a bigger | problem than TypeScript-specific, but still just syntax | sugar, syntax highlighted in the article. | | Const enums are erased at compile time. If you have a | reference to `MyEnum.VAR`, TS has to check whether the enum | is a const enum, and if so, replace with something like `1 | /* VAR */`. This means that the type information in one | file (where the enum is defined) is necessary to determine | the proper output of any file that uses it. | ragnese wrote: | Thank you for the answer! I won't waste your time because | I'm sure the answer is somewhere on the web, but off the | top of my head, I don't understand why TS "has" to emit | different JS. I would've assumed that the entire point of | a const enum is to inline the raw value in the emitted | JS, and thus, the programmer should be careful to | remember/know that the code is dealing with raw | ints/strings. | russellsprouts wrote: | You potentially get a problem for every '.' expression. | In this code: import {Something} from | './file'; console.log(Something.PROP); | | TypeScript doesn't know what to emit without type | information. If Something is a class, then the JS will | look the same. But if it's a const enum, then TypeScript | has to erase the Something.PROP expression and replace it | with the constant value of that enum member, since | Something will not exist at runtime. | [deleted] | conaclos wrote: | You can also name your type with the same name as the value: | export const Role = { CUSTOMER: 'customer', | ADMIN: 'admin', SYSTEM: 'system', STAFF: | 'staff', } as const export type Role = typeof | Role[keyof typeof Role] | joshstrange wrote: | :facepalm: of course I can, I don't know why it never | occurred to me. Probably a case of "if it's ain't broke" but | going forward I'll probably switch to this style. | conaclos wrote: | For a long time I avoided to use the same name for a type | and a value because I was afraid of possible breaking | change in the feature. | | And then I realized that this is an intended feature of | TypeScript: type merging. Here the type `Role` merges with | the type of the value `Role :) | ragnese wrote: | I disagree. Enums are fine if they're string-only. It's only | the numeric enums that cause the issues everyone complains | about enums for. If there were a lint for making sure all enums | are string-only, it would be the best solution, IMO. | | AFAIK, your solution is (slightly) worse than an enum in | several ways and better in none: export enum | Role { CUSTOMER = 'customer', ADMIN = | 'admin', SYSTEM = 'system', STAFF = | 'staff', } | | I _think_ that implements everything yours does, but is easier | to grok and fewer lines /declarations. | sgt wrote: | We evaluated TS for a recent project but ended up with JS (and | VueJS 3). I have a feeling it would have taken much longer to | develop using TS, but lacking experience in TypeScript it's hard | to say. I find JS pretty neat for exploratory programming. | kwinten wrote: | I can assure you that doing it in TS would not have been any | slower and you would have gained all the benefits that static | type checking brings with it. | | VueJS 3 in particular is much better suited to use TS than its | predecessor (without any additional plugins). | sgt wrote: | Can probably add types later, and consider it once we get the | project underway. I am not saying you are wrong, but I have | heard TypeScript developers complain about the turnaround in | development being slower. | pjerem wrote: | Well, typescript being a superset of JavaScript, you can | use TypeScript compiler and just write plain JavaScript. | | The only difference is that you'll be warned when you write | inconsistent (buggy) code. And that your IDE will | autocomplete with only compatible values. | | There is absolutely no way typescript slows down | development, you're just totally free to ignore any or all | of it. But it will help you more than you imagine. | | tbf, the only valid reason not to use ts today is if your | code targets directly the browser without any build step | and is referenced as is by your html. | _zooted wrote: | Article makes no real good arguments about not using the private | keyword. It's more descriptive and carries knowledge from other | languages compared to putting a hashtag in front of a variable | name. | kwinten wrote: | I agree. Just because JS has its way of doing it does not mean | you need to stick to that. I mean, TS adds a bunch of new | keywords to the language that JS lacks. Why not simply stick to | those intuitive keywords rather than this weird variable naming | nonsense that JS has to use because it doesn't have access | modifiers of any kind? | 01acheru wrote: | > it has to generate new JavaScript code that doesn't exist in | the original TypeScript code | | That's nonsense, the code is there otherwise what are we talking | about? | | You just need to understand how some TS features map to JS, | that's all. | chrismorgan wrote: | I think you may have misunderstood the complaint, which is | perfectly valid. A few paragraphs earlier: | | > _The downside to enums comes from how they fit into the | TypeScript language. TypeScript is supposed to be JavaScript, | but with static type features added. If we remove all of the | types from TypeScript code, what 's left should be valid | JavaScript code. The formal word used in the TypeScript | documentation is "type-level extension": most TypeScript | features are type-level extensions to JavaScript, and they | don't affect the code's runtime behavior._ | | And: | | > _Most TypeScript features work in this way, following the | type-level extension rule. To get JavaScript code, the compiler | simply removes the type annotations._ | | > _Unfortunately, enums break this rule._ | | And then there is an explanation about why this is important: | it makes life hard for tooling, especially _fast_ tooling; for | in the absence of such features, JavaScript tooling can support | TypeScript with little bother, just dropping the TypeScript | bits and getting equivalent JavaScript; but in the presence of | such features, they have to either use tsc (slow!) or implement | more TypeScript-specific stuff. | | Compilation of most TypeScript features to JavaScript simply | _removes_ the TypeScript bits. Enums, however, have to be | _transformed_ , adding to the output JavaScript something that | was not in the source _JavaScript_. | antihero wrote: | esbuild seems to support enums fine | a_humean wrote: | It doesn't support one variant of them, const enums for | example. That ties you to tsc emit. Its pretty clear that | if the tsc team could they would remove enums and favour | literal unions. | leodriesch wrote: | I've just looked this up and it seems to support `const | enum` just fine[0]. I remember Babel not being able to | process `const enum`, since it goes across module | boundaries and Babel does not. | | [0]: https://github.com/evanw/esbuild/issues/128 | _under_scores_ wrote: | Personally I don't get the argument against enums. From what I | can tell it's purely to do with the symantics of what Typescript | _is_ , rather than any inherently bad property of enums. | armchairhacker wrote: | tldr: avoid features in TypeScript which must emit runtime code | when compiling to JavaScript - that is, any TypeScript-exclusive | feature that's not type annotations. | | Honestly most of these features are really useful, and as someone | who never wants to work in raw JavaScript, I wish they were just | added to JavaScript instead. Why shouldn't JavaScript have enums, | namespaces, private modifiers which aren't #, and decorators? JS | already gets new features which break backward-compatibility, | mine as well add features which fix compatibility with | TypeScript. | throw_m239339 wrote: | I completely disagree with that article. Instead of "avoid this | or that", one should be understanding how Typescript compiler | works and not treat it as a blackbox. Ultimately, Typescript | does compile to Javascript. | | One cannot use Typescript without understanding Javascript, | since every single Javascript library is not written in | Typescript. | withinboredom wrote: | One should also understand the machine code that a | traditional language compiles to and how the CPU executes it. | Ultimately, your language compiles to machine code. /s | | No one should need or worry about what an abstraction | compiles to unless they're specifically working on that | abstraction or there's a language bug. | throw_m239339 wrote: | Typescript compiler is first and foremost a type system for | Javascript. It's not an independent language. In fact it | broke backward compatibility many times to follow the ES | spec. | | > One should also understand the machine code that a | traditional language compiles to and how the CPU executes | it. Ultimately, your language compiles to machine code. /s | | If libraries you use are all written in machine code, then | sure, you should have an understanding of machine code. | Your comparison clearly doesn't work here. | | > No one should need or worry about what an abstraction | compiles to unless they're specifically working on that | abstraction or there's a language bug. | | When an abstraction is that leaky, it's barely an | abstraction. Typescript does force you to choose a | Javascript version as a compilation target. Obviously you | are forced to know what Javascript version supports what | feature because Typescript isn't going to polyfill every | missing feature depending on your Ecmascript target. | austincheney wrote: | This is how ES6 got classes. Developers wanted JS to be some | other language they favored more. In that case specifically | people were really hoping to make the language look and feel | like Java, probably because they were trained in Java and | couldn't figure out functions as first class citizens. | mikojan wrote: | The Java crowd indeed gave us god awful classes. | | But before that there were countless competing models for | creating objects or object factories. | | Obviously, JavaScript is an object oriented language, too. | You cannot escape that fact if you are determined to make the | browser paint anything. | | Classes effectively solved the "How?" | | To pretend you don't need object orientation in JavaScript is | really trying hard to make JavaScript into an entirely | different language. | austincheney wrote: | > But before that there were countless competing models for | creating objects or object factories. | | Java and C# had object factories even though in those | languages classes could not be avoided. People wanted | classes because they could not figure how to program | without them. | | > To pretend... | | Don't use _this_ or _new_ in your code and suddenly a | tremendous amount of your code is exposed as unnecessary | superfluous vanity. That isn't making the language into | something else. | mikojan wrote: | > Java and C# had object factories | | Object factories were competing models (plural) for | creating any object. A total replacement for classes and | the like, not an augmentation. | | Here's one such model: function | createCar(spec) { const {speed} = spec; | let position = 0; return Object.freeze({ | move() { position += speed; }, | get position() { return position; } | }); } | | And so you'd find this or any other model or multiple | competing models in the very same code base. | | It sucked. | | > Don't use this or new in your code | | You are going to be mutating the internal state of | objects. Using this and new or not. | throw_m239339 wrote: | I think class were needed because too many developers were | creating their own (incompatible) class systems on top of | prototypal inheritance (which is more verbose). | | The problem with Typescript is that too many Typescript | developers don't understand Javascript itself, which is an | completely different issue. That and the obsession for some | to reproduce JEE everywhere including in the browser... | moystard wrote: | What do you mean by reproducing JEE? Leveraging OOP in your | Javascript programs? | | I really dislike this tendency of certain Javascript | developers to qualify combining OOP with Javascript as "not | understanding the language". | tentacleuno wrote: | In my humble opinion, they're both fine. Use functions | where you need to, and do the same with classes. They're | both citizens of the language, so why not utilize them? | moystard wrote: | I agree with you. They both serve a purpose and should be | used when they make the most sense. | wdb wrote: | I always thought it was because of ActionScript 3 | throw_m239339 wrote: | You mean ES4, the revenge ;) | | Proxies and classes were definitely salvaged from | ES4/ActionScript/Jscript.net. | | We wouldn't be needing Typescript, had ES4 been adopted | (ironically Microsoft was against it, because | Silverlight...). | | Someone definitely needs to write a book about the whole | saga. | cromwellian wrote: | I'd go further. There's no strong argument for why code-gen is | bad. Why is a compilation process that is simply "remove type | annotations" inherently better than one that emits code, or | does code transformations, other than just personal preference, | aesthetics, or simplicity? | | About the only positive that is qualitatively different is the | simplicity of comparing the output to the input. But for | someone building a large typescript codebase, and who uses | sourcemaps, it's not really a big issue. There are many many | languages that compile-to-JS, and I feel that insisting on | 'purity' for purity's sake isn't really a good justification. | That, TS as a super-set of JS instead of as a isomorphic | mapping between constructs is a perfectly viable way to | innovate in the language space. | armchairhacker wrote: | I think TypeScript loosely mapping to JS is important if just | because JS sourcemaps _suck_ , like they're so often randomly | ignored in callstacks etc. And JS semantics are so subtle it | makes transpiling any other language a pain. | | But this doesn't justify removing _every_ codegen feature. | Namespaces and enums won 't make your code less reasonable, | and moreover they're just syntax sugar, they don't change | actual JS semantics. | tuyiown wrote: | I see the next to zero codegen in typescript as a strategy: | it removes all discutions about languages features besides | typing, guarantees next to zero issues in production in case | of code generation bug, making compiler deliveries safe and | avoid need of coordination in toolchain. | cromwellian wrote: | Enums and namespaces are hardly complex code gen or | generate 'issues'. People would often manually namespace in | JS a few years ago, and namespaces and enums can really | just be seen a lightweight holder object instances. Indeed, | enums in Java are class instances. | | Besides, there is an straightforward way to remove enums | from a program just like removing type annotations: Inline | them as static fields of an object. | | There is a simple syntactic transformation. | e.g. change 'enum' to 'const', add a '=' before the '{' | and use ':' instead of '=' const HttpMethod = { | Get: 'GET', Post: 'POST' }; | // now this no longer breaks const method = | HttpMethod.Post; | | Namespaces can be translated in almost the exactly same | way. | chrismorgan wrote: | Trouble with your const there as written is that you | don't have a type that's equal to "GET" | "POST". | Fortunately, this can be done without repetition or _too_ | much bother: const HttpMethod = { | Get: 'GET', Post: 'POST', } as const; | type HttpMethod = (typeof HttpMethod)[keyof typeof | HttpMethod]; | | Maybe wrap the `{...} as const` in Object.freeze(...) for | good measure. | | It'd be really nice if they'd improve the ergonomics on | this in some way (`type Values<T> = T[keyof T]` would | reduce it to `type HttpMethod = Values<typeof | HttpMethod>`, which is a start but not enough), to make | it a genuine and suitable alternative to enum (minus the | other frippery that's generated) and const enum (because | it's pure JavaScript, not an extension). | AbuAssar wrote: | You can use const enum and the ts compiler will just replace any | enum reference with its value inplace. | auggierose wrote: | Enums are fine, just don't forget to use Object.freeze(enumName) | afterwards!! | | Apart from that, because Typescript has powerful union types, not | using enum is perfectly fine as well, for example instead of: | enum Relation { Less = -1, Equal, Greater } | Object.freeze(Relation); | | you could do instead: const Less = -1; | type Less = -1; const Equal = 0; type Equal = 0; | const Greater = 1; type Greater = 1; type | Relation = Less | Equal | Greater; | | Apparently you need the additional "type Less" etc. declarations, | I would have thought it should work without. | | As for private and #, the biggest disadvantage of # is that it is | so slow currently. But that will change hopefully soon when # is | not compiled as WeakMaps by TypeScript. I would hope they compile | private to # later on, backwards compatibility be damned :-D | robpalmer wrote: | #private fields are not slow when used natively. | | It's true the down-levelled code that uses WeakMaps is slower. | The decision to downlevel is in the hands of the user and is | controlled by the tsconfig "target" option. | | The only environment that needs downlevelled #private fields is | IE11. | crabmusket wrote: | > We recommend the new #somePrivateField syntax for a | straightforward reason: these two features are roughly | equivalent. | | The author of the article surely knows the significant difference | between JS private fields and TS private fields: TS private | fields can be easily circumvented, whereas JS private fields | cannot. See TS playground link[1] | | I think this is a significant enough point that people should | _not_ be taught that TS 's private is just a different way of | doing the same thing. I've always said that TS is basically a | fancy linter, and sometimes that's exactly what you want. | | TS's private keyword communicates programmer intent, but lets you | do what you like when you really have to. Just like the rest of | TS. | | [1]: | https://www.typescriptlang.org/play?#code/MYGwhgzhAECC0G8BQ1... | Tyriar wrote: | Const enums specifically are great, since they disappear on | compile you can use them to internally document strings/numbers | with verbose names/comments without the cost of a const variable | that would appear after compilation. | | As for the fact that types cannot simply be stripped out, I've | found building using plain tsc and have the bundler target tsc's | output directory. This separation is needed since most tools | don't support TypeScript project references anyway which I find | extremely useful for organizing internal modules. | concerned_user wrote: | What is there left to use in the end? Type annotations? | | Maybe recommend not to use Typescript altogether then. Their only | reasoning for this seems to be that the features "be more likely | to break when using build tools other than the official | TypeScript compiler". | activitypea wrote: | Their reasoning is missing the forest for the trees. The point | is that most TS features do not map easily to JS, making any | interaction with the resulting code a pain. That includes any | static analysis of the JS output (not all tools support TS, | after all), debugging both with step by step and console logs, | and monitoring in prod. | | > What is there left to use in the end? Type annotations? | | I've always used TS for the types and nothing more, and been | happy with it. Seems to me that most of the JS community has | come to adopt this approach over time, and I don't see what's | bad about it. | chrismorgan wrote: | Type annotations are the whole _point_ of TypeScript. The rest | is mostly distraction and historical decisions that made sense | at the time but have aged poorly. | Zababa wrote: | > What is there left to use in the end? Type annotations? | | That's what Typescript mostly is, that's where its success | comes from. Type annotations for existing JS code. The vast | majority of people need to type existing JS code, and compile | to JS. A few minority need some specifics from the TS type | system. Outside of that, there are other options. | smoyer wrote: | The arguments against the four language features in this article | all boil down to it not being Javascript. If I wanted to write in | Javascript, I'd use files with a _.js_ extension. If you 've | picked Typescript, I don't think you should worry about whether | your code is also valid Javascript "if you remove all the type | information" (who's going to do that anyway.) | | Unless you're a solo developer, what's more important it to agree | on languages and conventions that apply to your team's projects. | Once that's done, any change to the team's work process should be | a conscious, measured decision. | | Note that I also used CoffeeScript 10ish years ago and still | consider it to be a superior experience to working in plain-old | Javascript. | rezonant wrote: | While I agree with the general sentiment, there's a bit of | pedantry for you here: Typescript itself removes all the type | information when you compile, it's not about the developer | doing that themselves outside the context of the compiler. But | that is a moot point with regard to the suitability of the | features as the article itself suggests-- TS doesn't break your | enums during compilation, a suitable JS representation is | emitted. | | Occasionally I think I might want to use enums in a place but | then find that other solutions like const collection objects or | plain constants works fine enough for the purpose at hand. I | don't think it's a problem if folks use enums though, it's | really not a big deal. | mhoad wrote: | I continue to maintain that despite all the nice features from | ES6 onwards working in straight JavaScript is psycho shit. It's | the NFTs of development. | | I'm genuinely looking forward to the day where we have other | viable options for the DOM. I see TypeScript at best as a | temporary band aid because you're still stuck in the god awful | NPM ecosystem at the end of the day. | rezonant wrote: | I disagree with almost all of this, but I will say have you | checked out Deno? It is in fact Typescript without the NPM | ecosystem (amongst many other interesting aspects) | mhoad wrote: | Honestly, I'm more waiting for WASM garbage collection to | officially land as a standard. | | It won't get me DOM level access but I can at least move a | lot of my code there. I'm also quietly hopeful that canvas | based rendering can make some huge improvements in the next | few years so it doesn't feel like Flash 2.0 but I'm ready | to at least _start_ thinking about letting go of the DOM as | the thing I have to care about. | | Until then I'm having a good time with Lit (lit.dev) for | building web apps that need to be super snappy and "web | feeling" which is still basically every customer facing | thing. | | But in a dream scenario I would way rather be writing apps | in Flutter which was at least built from the ground up for | building complex user interfaces in a sensible way, but | that whole ecosystem is still in some very early days on | the web and isn't a good choice right now for most things, | hoping that changes in a few years as they also seem to be | targeting the WASM + Canvas path and the web as a platform | isn't there on that yet and neither are they. | rezonant wrote: | There are a few big problems with using Canvas for UI on | the web. First and foremost is accessibility- there is no | way for your app to convey the information screen readers | are able to get from analyzing the DOM along with the | ARIA metadata that you (should) put into your markup. | Furthermore, users who have trouble using a mouse can use | the keyboard on the web, and it usually works very well | since the browser handles it and the browser has been | battle tested. You would need to implement your own | keyboard handling scheme (though I'm certain a ton of | apps just wouldn't bother). What about scrolling with | touch input? You would have to implement that too and | good luck making it as smooth and performant as the | system's own native scrolling (let alone making it use | the appropriate rubber banding- iOS and Android have | separate ways of doing this because Apple patented the | original iOS rubber band scrolling) | | Secondly, there is no way for automated agents to extract | content from your user interface. This includes search | engines, browser extensions, the browser itself, or your | end users. I think that goes against what the web is, and | I hope other devs agree. The mutability of HTML (and thus | the DOM that represents it at runtime) is a strength, not | a weakness | mhoad wrote: | This was my first reaction also and I think it's | understandable but having looked at proposed solutions to | it as well it actually doesn't seem like such a big deal. | | I apologise if I get some minor details wrong here as I | am doing this on a phone and recalling this from memory | because I don't have the time to grab the sources right | now. | | However... the short version of the plan to solve this | that I seem to recall to this: | | The Flutter team specifically seemed to indicate that | they are already used to operating in non DOM | environments where they have to support accessibility | across Android, iOS, MacOS, Linux and Windows that doing | it on web actually isn't that big a deal as it first | seems. | | They already have all of the code in place that builds a | full tree (like the DOM) which does a complete mapping | between every element (widgets in Flutter lingo) on the | canvas to their respective bits of accessibility info. | They then just take that tree and hook it up to the | respective accessibility APIs that each platform exposes. | | At no point have they indicated that this looked like it | was going to be a serious roadblock or challenge for | them. I believe them and they have a history of this | approach working elsewhere. | | There is just too much money riding on this investment | for them not to get accessibility 100% correct in a web | native way. | | From there if you are able to expose everything through | accessibility APIs then you presumably also have | everything you need as a search engine or an adblocker to | actively modify that canvas. | | This is also AFAIK already a solved problem for them in | other products where they are using canvas based | rendering such as Google Earth and Google Docs. | | I don't have the details beyond that right now sorry but | that passes the sniff test for me at least. | maronato wrote: | The article's premise is wrong imo. Typescript is a superset[1] | of javascript, so typescript-only syntactic sugar is entirely to | be expected. | | [1]: https://github.com/microsoft/TypeScript (repo description) | gwbas1c wrote: | Not saying I agree, but this is a completely valid viewpoint. | | When I initially saw Typescript, I thought the point was to add | features from strongly-typed languages and then transpile into | Javascript. (IE, a more modern version of GWT, a Java to | Javascript transpiler.) | | The point of the article, though, is that Typescript works best | when its extensions to the language can simply be dropped. | That's clearly a "we've worked with this for many years and | this is a big lesson from experience" statement, so I wouldn't | discount it. | kodemager wrote: | I recently picked up TypeScript and I sort of agree with the | author. It's just so much cleaner to use Types instead of | enums. That being said, I don't think enums are bad if there | is a good reason to use them. Checking if something is a type | of X isn't one such thing in my opinion, but that's probably | religion. | | Namespaces mKe no sense to me. It's probably because | Microsoft drives TypeScript, but even though I was a C# | developer for 10 years before moving on, they've just always | been terrible to me. Their functionality is the sort of thing | that is nice in theory, but really terrible in real world | projects that run for years with variously skilled developers | in a hurry. | | Private is silly to me, but this is mostly because classes | are silly to me. I can see why you'd want it if you use a lot | of classes, I just don't see why you would do that unless | you're trying to code C# in TypeScript. One of the things I | loved the most about switching from C# to Python was how easy | it was to use dictionaries and how powerful they were. The | combination of TypeScript interfaces, Types and maps is the | same brilliance with type safety. But once again, it's sort | of the thing where classes sometimes make sense, and when | they do, so might private. | iakov wrote: | Typescript works fine with it's extensions to the language. | In fact, I can't imagine how it would not work fine - that | would be a serious issue in the official compiler or tools. | | It's the other tools that author is/was using that are having | issues. It's silly to provide blanket statements about very | useful features like enums or namespaces just because some | third-party tool is struggling with them IMO. | donatj wrote: | The reasoning behind most of this, that the output JS is not a | strict subset of the input is silly and imho represent a | misunderstanding of what Typescript is going for. It's a full | fledged language that compiles to readable JS, not just | _annotated JavaScript_. | | There are many features in Typescript where it simply isn't just | outputting a subset of the input, and many of them are the best | parts of Typescript. | | If you just want JavaScript with types, there are other languages | that do that, but Typescript offers so much more. | Zababa wrote: | > It's a full fledged language that compiles to readable JS | | Compiling to readable JS is not one of TS's goals. For example: | | https://www.typescriptlang.org/play?noImplicitAny=false&targ... | donatj wrote: | That seems _very readable_ , especially in comparison to | something like Dart or Elm's output, both of which can output | thousands of lines from something as simple as your example. | | From the language goals | | > 4. Emit clean, idiomatic, recognizable JavaScript code. | | https://github.com/Microsoft/TypeScript/wiki/TypeScript- | Desi... | mhoad wrote: | I don't really see the value proposition of having a | compilation step that also prioritises readability when you | have source maps. | | I love Dart personally and I mostly see it's compile to | nonsense looking code as a feature not a bug because it's | an ACTUAL compilation step worked on by ex Chrome team | members who understand V8 internals not just code splitting | and running terser over it and calling it a day. Want to | get the same compilation optimisations that Google uses to | run all of their multi billion dollar ad business? Cool, | that's enabled by default out of the box. [1] | | The part where Dart on the web falls over for me is that | they have shitty support right now for modern web APIs. | They are building against some ancient version of Chrome's | WebIDL files so you can totally forget about things like | web components for example. | | So in that sense it doesn't feel like a sensible choice in | 2022 for basic web development which is a shame because | it's otherwise probably the best developer experience I've | ever seen. | | [1] I say this somewhat theoretically, I don't know that | Dart is in anyway an obvious thing to point to in terms of | web performance from what I had seen casually. I think | their goal there you can write huge business critical | applications with stupidly large code bases and still get | good performance. But nobody's experience after using | Google Ads is to talk about how snappy it was. | Zababa wrote: | > I don't really see the value proposition of having a | compilation step that also prioritises readability when | you have source maps. | | It's to increase adoption. Some people still remember | migrating to coffeescript and away from it. It's in line | with tsc accepting regular JS files, the degrees of | strictness, things like that. Typescript is optimized to | be adopted by the maximum number of people, which in turn | increases its usefulness, the feedback they can get, | their influence on JS. People are going to write bindings | for popular libraries, even migrate them. | | Some other people (like at my job) have some people use | typescript, and others the generated code. It makes | debugging and reasoning about code easier. | | As for Dart, I'm not really convinced. The language seems | to have the same philosophy as Go (incremental | improvement over old technologies), and while for Go it | works because Go is relatively "low level" (lower than | Dart), for Dart it's just weird. | mhoad wrote: | If you're going to be working in a multi language code | base I am with you. Handing people garbage looking | generated or compiled code and saying work with this is | going to require a solid set of well defined interfaces | at a minimum and maybe like you said even giving up all | of that and having to make one thing look like the other. | All of what I said was under the assumption that you | don't need to think about JavaScript again. | | Im making the argument that JavaScript is a target that | we have all been collectively forced into due to the | limitations of the web as a platform but short to medium | term horizon that is changing where things like WASM are | maturing and will let a lot of new options flourish (.NET | folks seem to be probably leading this charge currently) | | But just stopping to think about the implications of that | kind of changing landscape and what's coming, I don't | think aiming for 100% JS interop not just from a code | perspective but it also the entire tooling and developer | ecosystem perspective is going to be as important. | | Again, I just think people are somewhat forced to at the | moment because the web has always been a one language | show. That wasn't because JS was the best choice but | rather a limitation of the platform itself which is | already in the early stages of changing. | | For Dart specifically I kind of get what you're talking | about I guess because it's pretty commonly referred to as | the best bits of JS and Java put together while ditching | the worst parts of each so it's clearly aimed at | productivity for application sized code bases rather than | something low level but again... that's literally why | they have a proper complication step because getting it | down to something a lot more low level is exactly what a | compiler is for. That doesn't feel weird to me at all, | that actually feels like an incredibly sensible choice. | Vinnl wrote: | > imho represent a misunderstanding of what Typescript is going | for. | | It's pretty what TypeScript's going for though: | | > Avoid adding expression-level syntax. | | https://github.com/Microsoft/TypeScript/wiki/TypeScript-Desi... | donatj wrote: | I'm curious what they mean by "expression-level syntax" | specifically, because they certainly added a lot of _syntax_ | that isn't ECMA and aren't just Babel, backporting future | ECMA features. | conaclos wrote: | Replacing TS enum with JS objects is really tedious: | type HttpMethod = keyof typeof HttpMethod const | HttpMethod = { GET: "GET", POST: "POST", | } as const | | It is even worse with int enum: type HttpMethod = | Extract<keyof typeof HttpMethod, number> const | HttpMethod = { GET: 0, POST: 1, 0: | "GET", 1: "POST", } as const | | My personal "avoid"-rules are: | | - Avoid enum with negative integers because it generates code | that cannot be optimized by the JS engine | | - Prefer ESM modules to namespaces, because the first can be | tree-shaked. | joshstrange wrote: | I've been using the method I outlined here [0] and it's not | tedious to work with at all. That said you might have a | different usecase where my example doesn't work. | | [0] https://news.ycombinator.com/item?id=30010017 | Tade0 wrote: | I thought this is going to be about dependent types or the | `infer` keyword(or any other badly documented feature like the | latter). | | There are features of the type system you should definitely avoid | unless you're writing a library. Enums aren't it. | jacobr wrote: | The TypeScript documentation even states "In modern TypeScript, | you may not need an enum when an object with as const could | suffice ... The biggest argument in favour of this format over | TypeScript's enum is that it keeps your codebase aligned with the | state of JavaScript, and when/if enums are added to JavaScript | then you can move to the additional syntax" | https://www.typescriptlang.org/docs/handbook/enums.html#obje... | ameliaquining wrote: | This whole thing feels basically grounded in purity over | practicality. In general it's a good idea to write idiomatic | TypeScript. Even when I agree with the given recommendations, the | given reasons don't seem like the strongest ones. | | I most strongly disagree with the recommendation against enums. | Realistically, you will probably never run into a compiler bug | from enum emit; maybe something like this might happen with a | very complicated runtime feature, but enum emit is dead-simple | and hard to get wrong (at least if your toolchain has any tests | at all, which it presumably should). And they're generally | convenient and fill a useful niche, especially numeric enums with | implicitly assigned values. (I'm also curious what the article's | authors think of const enums.) | | Namespaces have been soft-deprecated, modules are pretty much | just better, and so I quite agree that you shouldn't use them, | though I'm not sure the risk of compiler bugs is the most | compelling argument against. (It is more compelling than with | enums, since the required code transformations are much less | trivial.) | | Decorators, especially with metadata, facilitate lots of useful | things that otherwise just aren't possible in TypeScript. It's | also the case (though the authors seem unaware of this) that they | will _never_ be standardized in the current form that TypeScript | has them, because they were based on an earlier version of the | design that has since been pretty explicitly rejected. The risk | isn 't that decorators are never standardized; if that happens | then TypeScript will just keep the current design forever and | things will be mostly fine. The risk is that they get | standardized in an _incompatible form_ and then you have an | interesting migration ahead of you. TC39 won 't do this lightly, | but no one knows exactly what the future holds. So it is a | tradeoff to think carefully about, though in the end reasonable | people will disagree. | | # vs. private is mostly a matter of style/taste, with two | exceptions. First, if you have any code on your page that you | don't trust not to be doing weird things, strongly consider #, | since it provides protection against corruption of internal | state. Second, if you have to support older browsers that don't | support #, then don't use it; the compiler can downlevel it, but | only at significant code-size and performance cost that you don't | want to pay (and debugging it in the browser will also be | annoying). | | Do the authors also disfavor parameter properties? Those also | require emit beyond just stripping types, but are super- | convenient and useful and don't really conceptually complicate | things. | | Incidentally, the feature at the top of my own list of | "TypeScript features to avoid" (other than namespaces and other | soft-deprecated features) is something entirely different: | conditional types. Most other advanced type-system features | behave reasonably predictably most of the time, but conditional | types are very demanding of maintainers' knowledge of fiddly | type-system details. I'm not saying it's never worth it (and in | particular the built-in utility types are usually fine even | though they're conditional under the hood), but whenever possible | I try to reach for something else. | activitypea wrote: | > you will probably never run into a compiler bug from enum | emit; | | That's true, but diagnosing other bugs is an absolute pain in | the butt when your enum value at runtime is 0, 1, or 2. You get | all of the readability of C with none of the performance :) | ameliaquining wrote: | I personally haven't found that to be much of a hindrance to | debugging, but I suppose this is somewhat a matter of | personal preference. | deckard1 wrote: | > good idea to write idiomatic TypeScript | | Is there really such a thing? Everyone seems to be writing TS | with the "fake it until you make it" mantra, never quite | reaching the "make it" phase. People still use "interface" and | "type" interchangeably without rhyme or reason. Or "import" vs | "import type". No one knows what they are doing in TS. Or why. | Just look at this entire comment section. | heisenbit wrote: | > Decorators, especially with metadata, facilitate lots of | useful things | | Angular 2 would look very different without it. ___________________________________________________________________ (page generated 2022-01-20 23:01 UTC)