What 4 Years Of Programming Taught Me About Writing Typed JavaScript
Untyped JavaScript, TypeScript, Flow, and PropTypes: Which one should you use?
Types of types
Mostly, statically typed languages are criticized for restricting developers. On the other hand, they’re loved for bringing early information about errors, documenting components (such as modules, methods, etc.), and now other more advanced functionalities such as auto-completion.
A preliminary study from 2009 on untyped languages gives us some reference on exactly those pros and cons. Today, another type of language is also widely used: dynamically typed languages.
A dynamically typed language is different from its counterpart by bringing types but at runtime. This way, you can have far more freedom than strongly typed languages while keeping their advantages.
From our list, we have a single dynamically typed language: TypeScript. And that’s not completely exact, TS could also be called a soft-typed language, that is between a dynamically and statically typed language. As this is not today’s subject, curious readers can have a look at the following article:
Why am I saying there is only one? Of course, JavaScript is considered untyped (or weakly typed), while PropTypes is a package allowing type-checking at runtime.
Flow is … neither. In practice, it looks a lot like TypeScript, and both are often compared. Inside your IDE and CLI, they are similar but their engines differ: TypeScript is a language while Flow is called a “static type checker”.
In Flow, you write “annotations” to set types. At compile time, those annotations must be removed, which creates JavaScript files without any superset.
It has been an argument in favor of Flow: performance. Both solutions have almost the same functionalities, but Flow removes any overhead that TypeScript has, once compiled.
My experience
I started my career in the JavaScript & front-end world in 2016 with Angular2 (and TypeScript).
Before this front-end project, I mainly worked on C#, Java, and a bit of Vanilla JavaScript. I hated it. To me, vanilla JavaScript had no structure, type, or object-oriented concepts, it was HELL.
With more experience, practice with Angular2 (shipped with TypeScript) and then React, I almost started to enjoy it. That’s when I seriously considered a way to type JavaScript: I used TypeScript for a short time (which I didn’t master at the time) but I was back to untyped JavaScript with React.
React with untyped JavaScript was working not-so-bad but I felt like a piece was missing:
After a few weeks on a React project, code could easily get messy and difficult to understand, even more for newcomers.
We needed a lot of time to read a piece of code.
The number of errors after builds was too high.
Among the practices needed to avoid these problems, my experience told me that typed JavaScript was the top priority. After some research: here I was, looking at TypeScript, Flow, and PropTypes.
PropsTypes allowed me to type-check my props but that was not enough. What about types outside of my React components? Can I validate that my app is type-safe as part of my CI pipeline? Can I validate it as a commit hook?
Well, You can find some ways to validate types during your tests and use them outside of components but PropTypes was not designed with that in mind. It was intended to give you information in real time (at runtime).
As I was not convinced by PropTypes, I was left with two choices: TypeScript and Flow. Functionality-wise they looked the same, with some good points on Flow side:
No overhead meant better performance.
Backed by Facebook, as well as React.
Those were enough to make a difference and that’s why I started using Flow with React.
Flow
Around 2 years. That’s the time I’ve been using Flow and only Flow. It worked with React / React Native like a charm, boosted our team productivity, was a great tool as part of our CI pipeline, and generally helped us deliver.
And then, we hit a wall. Once our projects started getting bigger, flow server struggled to start, and was getting slower and slower. At some point we were simply unable to run it, our CI running time and its cost skyrocketed.
Unfortunately, at the same time, I started getting into other projects. Among them: an embedded project with Arduino using johnny-five. That’s when I realized the second weakness of Flow: its community support.
That’s how typing systems in JavaScript work: you write your module with one solution and write an independent type definition for the other. TypeScript had a lot of support, I think that even today, the number of libraries without support that I used is less than 10.
Flow was different: during the time I used it in the React ecosystem, there were a few libraries without support but I could still work without it. Outside of this ecosystem, support was a mess, I was unable to find even one library working with Flow. I found solutions to transform TypeScript definitions to Flow but in the end, it didn’t work.
At some point, I wondered if I would really have to use TypeScript, even for React / RN projects. How inconsistent is that: using a technology backed by a company such as Facebook, and replacing its typing system with one from Microsoft? Moreover, support for TypeScript in React must be a mess, right?
TypeScript
I (re)discovered a whole new world.
After, getting back to TypeScript on side projects with Johnny-Five or React, I couldn’t believe how I loved it.
Not only did my performance and library support problems disappear, but I got to love its syntax. Since 2016 and a lot of updates, I could not find anything to reproach TypeScript for.
React / React Native support was perfect, with the whole ecosystem such as eslint with prettier, jest, etc. Also, an extract from State of JavaScript (2022) gives us an idea of the difference between TypeScript and other flavors:
My next move was simple: As I previously wrote templates/boilerplates for my team using Flow, they were quickly replaced with the equivalent in TypeScript. Since then, we’ve only been using TypeScript.
Flow was great for a long time, its performance and support problems got the best of him while TypeScript later evolved to be amazing: my choice was simple.
Conclusion
If you were to ask me what to choose between untyped JavaScript, PropTypes, Flow, and TypeScript for your project: I would tell you the following:
Untyped JavaScript can be a good choice if you work on a project for less than a week, throw it after, and don’t wish to learn any type solution.
PropTypes is a great tool if, for any reason, you cannot use Flow or TypeScript but is not enough for a big project.
Flow was great, since then I did not try it again, but would not bet on it.
TypeScript is a great solution, in my own opinion the best out there and required for any JavaScript project with high stakes.
Regarding using a typing library I experienced some objections, that I would like to answer here.
Loss of performance: today’s solutions are great and the infinitely small loss of performance you could sustain would be balanced by the structure that typed code brings.
Lack of knowledge in TypeScript / Flow: if your project has high stakes and you don’t want to use typed JavaScript because your developers don’t know it, the project is bound to fail. Change your team or train them, that’s the only way.
Loss of time: I’ll quote Clean Code: A Handbook of Agile Software Craftsmanship from Robert C. Martin: “Indeed, the ratio of time spent reading vs. writing is well over 10:1.” If one thing, typed JavaScript will help you read code, thus increasing your productivity rather than decreasing it.
TL;DR
TypeScript.
Photo by Kevin Canlas on Unsplash