[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)