I've been using TypeScript now for about a year. Hardly enough time to come to a conclusion about it, but I've wanted to write this article for months. Pushing it off each time hoping that it would finally click. I also decided that I might not be the best judge as being a library writer I was sort of thrust right over the deep end. So I wanted to give people of different experience levels and programming backgrounds I trusted an unbiased chance at it. So not only did I convert all my open-source libraries to TypeScript, but 6 months later I asked the developers at the startup I work at if they would like to use TypeScript on a rewrite of our core application. They had a varied interest in learning it, but they were all open to it. Now that several more months have passed, I finally feel that I'm at a point I can say something. So let's dig in.
I've been programming for about 25 years now. I've used dozens of typed languages over the years. But TypeScript was a first in that it was trying to put types on top of a dynamic language. This in itself seems like it would be an incredible feat. But then again dynamically typed languages did so a few decades ago. At one point getting rid of the types was actually seen as progress.
When you start with simple examples it all seems familiar enough. You add a few annotations and marvel at how it won't let you assign a string to a number. You make sure your functions have clear parameters and return types and you start feeling like you are getting it. And then you hit a place where you need to pass in different objects. Your first thought is loosening up the definition but then you see an example with generics and realize TypeScript uses generics way more liberally than you are used to with C++ or Java. Even cooler, their type can often be inferred which means you don't even need to annotate and everything magically works.
That is until you add a few extra levels on to, and you start coming across the inconsistencies, or the places where types can't be inferred. The other day I was helping my lead dev work through some typings on factory function that produces hooks that return CSS in JS generated classes that are a result of the style definition passed into the factory and props passed into the hook. He had something very basic and couldn't quite figure out why his types weren't working. So I sat down and started using generics to assign multiple values and creating wrappers to project types for return values. Someone how after a couple of tries I got it working for the most part. I admit I felt pretty good about myself, but the developer looked bewildered. You see he thought he was finally getting TypeScript and he had no idea what I had just done. So I spent the next half an hour explaining it. In the end, he got it, but he still didn't feel any better about it as he would have never thought about it that way. And truthfully I was in the same boat months earlier.
This leads to a very strange scenario that the more complex the issue even when you go for help, communicating the intent of the code is as important as the function. When talking about possible solutions it isn't unlike people looking at modern art trying to critique the intent and the emotion of a toilet paper roll nailed to a wall. You can spend hours perfecting an elegant solution to your types without shipping any new workable code. It makes you feel really good and clever when you get it right. It is metaprogramming to the highest degree. It gets even more awkward when you are trying to use a 3rd party library who is more concerned about spending several months getting it right than getting something out that works (while in meanwhile the current types are effectively broken).
As I alluded to previously, programming itself has these characteristics, but it's super strange when your tools do too. That level of uncertainty, that need to solve a puzzle with your tools completely on the side of the programming problem you are solving is the kind of thing that I can see developers liking given their personality as problem solvers, but when it comes down to things like efficiency and productivity it is excess. Every time I use TypeScript and I realize that I remember being that young and unexperienced programmer just doing a lot of unnecessary stuff.
? (optional chaining operator). Then you realize your logic was wrong in the 2nd loop. So you need to do a refactor.
What I've witnessed with TypeScript is that 30 line CoffeeScript file is now 150 lines. I can't see the whole thing in my IDE window anymore without scrolling. At about the same time the CoffeeScript developer is starting the refactor the TypeScript developer has just reconciled all the types and is about to run their code for the first time. Type annotation doesn't take much time unless you need to look up Types you don't know (seriously for the browser MDN is such a lifesaver here), but the tendency here is to ensure everything matches that it all works out the first time you run it. Sure the TypeScript developer never has that run where the browser spits out
Cannot read 'name' of undefined but by the time they are realizing their logic is wrong in the 2nd loop our first developer is already testing the refactor.
As in it has a higher noise to signal ratio. Less of the code you are looking at is functional. I've already talked about more code being required but this goes beyond initial development. I know this is perhaps more opinion based but when Dan Abramov (React Core Team) recently tweeted that when he looks at someone else's code the Types actually get in the way of him seeing the code, I realized I wasn't alone. Type information can be noise when you are just trying to see the flow. Truthfully this is less of an issue compared to the last as it doesn't change how you approach coding. But it is something. We can filter out the annotations pretty readily but simply function declarations going from one line to 5 lines starts you on a path where you are always looking at less.
Ultimately I settled on not having custom bindings with
Obviously, as a lower-level library writer, I hit these right away, but I've even seen these affect application developers. They've had to change the way they would approach an interopt layer since it wasn't as TypeScript friendly. Similarly hitting weird TypeScript only idiosyncrasies when using 3rd party libraries. Pretend you haven't sold your soul to TypeScript and read this guide for Material UI. Why would I ever sign up for this?
On my team, it was split. The backend developers did not have a hard time with TypeScript and their solution scaled with their knowledge as they've found better ways to structure their project. However, on the frontend, it has been nearly a disaster. TypeScript has basically dictated other technology choices. Where we've been like use TypeScript or use this library. So far we've sided with TypeScript because of the promise of what it brings. In hindsight, I would have never introduced it there but I feel like we are starting to get over the hump so the time invested is worth seeing it through. It's just ironic that many of the advertised benefits I believe are actually detrimental.
TypeScript might not be the language we need, but it is the language we deserve for now.