Live data from Hacker News

TypeScript Features to Avoid

executeprogram.com

151–160 of 212 posts

Re: TypeScript Features to Avoid

#151
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 {}
    // 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 {}
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...

Re: TypeScript Features to Avoid

#152

Earlier quoted context omitted.

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'

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…

>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.

Re: TypeScript Features to Avoid

#153

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…

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.

Re: TypeScript Features to Avoid

#155

Earlier quoted context omitted.

I use "class" all the time in Javascript (don't use Typescript at all), why wouldn't you?

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 ol…

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.

Re: TypeScript Features to Avoid

#156

Earlier quoted context omitted.

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…

>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.

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.

Re: TypeScript Features to Avoid

#157

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…

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.

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...

Re: TypeScript Features to Avoid

#158
post #136

Earlier quoted context omitted.

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)

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…

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

Re: TypeScript Features to Avoid

#159

Earlier quoted context omitted.

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'

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…

> 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.

Re: TypeScript Features to Avoid

#160
post #83

Earlier quoted context omitted.

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.

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...

Symbol is an extremely heavy hammer. It's also difficult to use correctly.

What value do you think it offers here?

Post reply on HN